[orx-dnk3] Add feature architecture and post processing effects
This commit is contained in:
@@ -16,6 +16,13 @@ class PerspectiveCamera(var node: SceneNode) : Camera() {
|
||||
var far = 100.0
|
||||
var near = 0.1
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = aspectRatio.hashCode()
|
||||
result = 31 * result + fov.hashCode()
|
||||
result = 31 * result + far.hashCode()
|
||||
result = 31 * result + near.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class OrthographicCamera(var node: SceneNode) : Camera() {
|
||||
@@ -29,4 +36,12 @@ class OrthographicCamera(var node: SceneNode) : Camera() {
|
||||
var yMag = 1.0
|
||||
var near = 0.1
|
||||
var far = 100.0
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = xMag.hashCode()
|
||||
result = 31 * result + yMag.hashCode()
|
||||
result = 31 * result + near.hashCode()
|
||||
result = 31 * result + far.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
package org.openrndr.extra.dnk3
|
||||
|
||||
fun dryRenderer() : SceneRenderer {
|
||||
val sr = SceneRenderer()
|
||||
return sr
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package org.openrndr.extra.dnk3
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.math.Matrix44
|
||||
import org.openrndr.math.transforms.perspective
|
||||
|
||||
|
||||
class Geometry(val vertexBuffers: List<VertexBuffer>,
|
||||
@@ -14,19 +15,28 @@ class Geometry(val vertexBuffers: List<VertexBuffer>,
|
||||
override fun toString(): String {
|
||||
return "Geometry(vertexBuffers: $vertexBuffers, indexBuffers: $indexBuffer, primitive: $primitive, offset: $offset, vertexCount: $vertexCount)"
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = 0
|
||||
result = 31 * result + primitive.ordinal.hashCode()
|
||||
result = 31 * result + offset.hashCode()
|
||||
result = 31 * result + vertexCount.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
val DummyGeometry = Geometry(emptyList(), null, DrawPrimitive.TRIANGLES, 0, 0)
|
||||
|
||||
sealed class Entity
|
||||
|
||||
class MeshPrimitive(var geometry: Geometry, var material: Material) {
|
||||
|
||||
override fun toString(): String {
|
||||
return "MeshPrimitive(geometry: $geometry, material: $material)"
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = geometry.hashCode()
|
||||
result = 31 * result + material.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class MeshPrimitiveInstance(val primitive: MeshPrimitive, val instances: Int, val attributes: List<VertexBuffer>)
|
||||
@@ -36,6 +46,9 @@ class Mesh(primitives: List<MeshPrimitive>) : MeshBase(primitives) {
|
||||
override fun toString(): String {
|
||||
return "Mesh(primitives: $primitives)"
|
||||
}
|
||||
override fun hashCode(): Int {
|
||||
return primitives.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
class SkinnedMesh(primitives: List<MeshPrimitive>,
|
||||
@@ -49,10 +62,7 @@ class InstancedMesh(primitives: List<MeshPrimitive>,
|
||||
var attributes: List<VertexBuffer>) : MeshBase(primitives)
|
||||
|
||||
|
||||
class Fog : Entity() {
|
||||
var color: ColorRGBa = ColorRGBa.WHITE
|
||||
var end: Double = 100.0
|
||||
}
|
||||
data class Fog(var color: ColorRGBa = ColorRGBa.WHITE, var end : Double = 100.0) : Entity()
|
||||
|
||||
abstract class Light : Entity() {
|
||||
var color: ColorRGBa = ColorRGBa.WHITE
|
||||
@@ -61,4 +71,18 @@ abstract class Light : Entity() {
|
||||
abstract class Camera : Entity() {
|
||||
abstract val projectionMatrix: Matrix44
|
||||
abstract val viewMatrix: Matrix44
|
||||
}
|
||||
|
||||
abstract class CubemapProbe : Entity() {
|
||||
open val projectionMatrix: Matrix44
|
||||
get() {
|
||||
return perspective(90.0, 1.0, 0.1, 150.0)
|
||||
}
|
||||
var dirty = true
|
||||
}
|
||||
|
||||
class IrradianceProbe: CubemapProbe() {
|
||||
override fun hashCode(): Int {
|
||||
return true.hashCode()
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,6 @@ abstract class FacetCombiner(val facets: Set<FacetType>, val targetOutput: Strin
|
||||
override fun toString(): String {
|
||||
return "FacetCombiner(facets=$facets, targetOutput='$targetOutput')"
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
abstract class ColorBufferFacetCombiner(facets: Set<FacetType>,
|
||||
@@ -107,6 +105,14 @@ class NormalFacet : ColorBufferFacetCombiner(setOf(FacetType.WORLD_NORMAL), "nor
|
||||
override fun generateShader(): String = "o_$targetOutput = vec4(v_worldNormal.rgb, 1.0);"
|
||||
}
|
||||
|
||||
class ViewDepthFacet : ColorBufferFacetCombiner(setOf(FacetType.VIEW_POSITION), "viewDepth", ColorFormat.R, ColorType.FLOAT16) {
|
||||
override fun generateShader(): String = "o_$targetOutput.r = v_viewPosition.z;"
|
||||
}
|
||||
class ClipDepthFacet : ColorBufferFacetCombiner(setOf(FacetType.CLIP_POSITION), "clipDepth", ColorFormat.R, ColorType.FLOAT32) {
|
||||
override fun generateShader(): String = "o_$targetOutput.r = gl_FragCoord.z;"
|
||||
}
|
||||
|
||||
|
||||
class ViewPositionFacet : ColorBufferFacetCombiner(setOf(FacetType.VIEW_POSITION), "viewPosition", ColorFormat.RGB, ColorType.FLOAT32) {
|
||||
override fun generateShader(): String = "o_$targetOutput.rgb = v_viewPosition.rgb;"
|
||||
}
|
||||
@@ -125,11 +131,28 @@ class FragmentIDFacet: ColorBufferFacetCombiner(setOf(FacetType.FRAGMENT_ID), "f
|
||||
}
|
||||
}
|
||||
|
||||
class LDRColorFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR), "color", ColorFormat.RGBa, ColorType.UINT8) {
|
||||
class LDRColorFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR, FacetType.EMISSIVE), "color", ColorFormat.RGBa, ColorType.UINT8) {
|
||||
override fun generateShader() = """
|
||||
vec3 finalColor = (max(vec3(0.0), f_diffuse.rgb) + max(vec3(0.0),f_specular.rgb) + max(vec3(0.0), f_emission.rgb)) * (1.0 - f_fog.a) + f_fog.rgb * f_fog.a;
|
||||
vec3 finalColor = (max(vec3(0.0), f_diffuse.rgb) + max(vec3(0.0),f_specular.rgb) + max(vec3(0.0), f_emission.rgb) + max(vec3(0.0), f_ambient.rgb)) * (1.0 - f_fog.a) + f_fog.rgb * f_fog.a;
|
||||
o_$targetOutput = pow(vec4(finalColor.rgb, 1.0), vec4(1.0/2.2));
|
||||
o_$targetOutput *= m_color.a;
|
||||
|
||||
"""
|
||||
}
|
||||
|
||||
class HDRColorFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR, FacetType.EMISSIVE), "color", ColorFormat.RGBa, ColorType.FLOAT16) {
|
||||
override fun generateShader() = """
|
||||
vec3 finalColor = (max(vec3(0.0), f_diffuse.rgb) + max(vec3(0.0),f_specular.rgb) + max(vec3(0.0), f_emission.rgb) + max(vec3(0.0), f_ambient.rgb)) * (1.0 - f_fog.a) + f_fog.rgb * f_fog.a;
|
||||
o_$targetOutput = vec4(finalColor.rgb, 1.0);
|
||||
o_$targetOutput *= m_color.a;
|
||||
"""
|
||||
}
|
||||
|
||||
class DiffuseIrradianceFacet : ColorBufferFacetCombiner(setOf(FacetType.DIFFUSE, FacetType.SPECULAR), "color", ColorFormat.RGBa, ColorType.UINT8) {
|
||||
override fun generateShader() = """
|
||||
vec3 finalColor = (max(vec3(0.0), f_diffuse.rgb) + max(vec3(0.0), f_emission.rgb));
|
||||
o_$targetOutput = vec4(finalColor.rgb, 1.0);
|
||||
|
||||
|
||||
"""
|
||||
}
|
||||
13
orx-dnk3/src/main/kotlin/Feature.kt
Normal file
13
orx-dnk3/src/main/kotlin/Feature.kt
Normal file
@@ -0,0 +1,13 @@
|
||||
package org.openrndr.extra.dnk3
|
||||
|
||||
import org.openrndr.draw.Drawer
|
||||
|
||||
interface Feature {
|
||||
fun <T : Feature> update(
|
||||
drawer: Drawer,
|
||||
sceneRenderer: SceneRenderer,
|
||||
scene: Scene,
|
||||
feature: T,
|
||||
context: RenderContext
|
||||
)
|
||||
}
|
||||
@@ -18,11 +18,15 @@ interface AttenuatedLight {
|
||||
}
|
||||
|
||||
class DirectionalLight(var direction: Vector3 = -Vector3.UNIT_Z, override var shadows: Shadows = Shadows.None) : Light(), ShadowLight {
|
||||
var projectionSize = 10.0
|
||||
var projectionSize = 50.0
|
||||
|
||||
override fun projection(renderTarget: RenderTarget): Matrix44 {
|
||||
return ortho(-projectionSize / 2.0, projectionSize / 2.0, -projectionSize / 2.0, projectionSize / 2.0, 1.0, 150.0)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return color.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
class SpotLight(var direction: Vector3 = -Vector3.UNIT_Z, var innerAngle: Double = 45.0, var outerAngle: Double = 90.0) : Light(), ShadowLight, AttenuatedLight {
|
||||
@@ -33,16 +37,46 @@ class SpotLight(var direction: Vector3 = -Vector3.UNIT_Z, var innerAngle: Double
|
||||
override fun projection(renderTarget: RenderTarget): Matrix44 {
|
||||
return perspective(outerAngle * 2.0, renderTarget.width * 1.0 / renderTarget.height, 1.0, 150.0)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = direction.hashCode()
|
||||
result = 31 * result + innerAngle.hashCode()
|
||||
result = 31 * result + outerAngle.hashCode()
|
||||
result = 31 * result + constantAttenuation.hashCode()
|
||||
result = 31 * result + linearAttenuation.hashCode()
|
||||
result = 31 * result + quadraticAttenuation.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class HemisphereLight(var direction: Vector3 = Vector3.UNIT_Y,
|
||||
var upColor: ColorRGBa = ColorRGBa.WHITE,
|
||||
var downColor: ColorRGBa = ColorRGBa.BLACK) : Light() {
|
||||
var irradianceMap: Cubemap? = null
|
||||
override fun hashCode(): Int {
|
||||
var result = direction.hashCode()
|
||||
result = 31 * result + upColor.hashCode()
|
||||
result = 31 * result + downColor.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class PointLight(var constantAttenuation: Double = 1.0,
|
||||
var linearAttenuation: Double = 0.0,
|
||||
var quadraticAttenuation: Double = 0.0) : Light()
|
||||
var quadraticAttenuation: Double = 1.0) : Light() {
|
||||
override fun hashCode(): Int {
|
||||
var result = constantAttenuation.hashCode()
|
||||
result = 31 * result + linearAttenuation.hashCode()
|
||||
result = 31 * result + quadraticAttenuation.hashCode()
|
||||
result = 31 * result + color.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class AmbientLight : Light()
|
||||
class AmbientLight : Light() {
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return color.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package org.openrndr.extra.dnk3
|
||||
|
||||
import org.openrndr.draw.Cubemap
|
||||
import org.openrndr.draw.RenderTarget
|
||||
import org.openrndr.draw.ShadeStyle
|
||||
import org.openrndr.draw.shadeStyle
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.extra.dnk3.features.IrradianceSH
|
||||
import org.openrndr.math.Vector3
|
||||
|
||||
interface Material {
|
||||
val name: String?
|
||||
var doubleSided: Boolean
|
||||
var transparent: Boolean
|
||||
val fragmentID: Int
|
||||
@@ -14,6 +14,7 @@ interface Material {
|
||||
}
|
||||
|
||||
class DummyMaterial : Material {
|
||||
override var name: String? = null
|
||||
override var doubleSided: Boolean = true
|
||||
override var transparent: Boolean = false
|
||||
override var fragmentID = 0
|
||||
@@ -42,8 +43,14 @@ data class MaterialContext(val pass: RenderPass,
|
||||
val lights: List<NodeContent<Light>>,
|
||||
val fogs: List<NodeContent<Fog>>,
|
||||
val shadowMaps: Map<ShadowLight, RenderTarget>,
|
||||
val meshCubemaps: Map<Mesh, Cubemap>
|
||||
)
|
||||
val meshCubemaps: Map<Mesh, Cubemap>,
|
||||
val irradianceProbeCount: Int
|
||||
) {
|
||||
var irradianceSH: IrradianceSH? = null
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
data class PrimitiveContext(val hasNormalAttribute: Boolean, val hasSkinning: Boolean)
|
||||
|
||||
|
||||
@@ -2,15 +2,17 @@ package org.openrndr.extra.dnk3
|
||||
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.extra.dnk3.cubemap.glslEvaluateSH
|
||||
import org.openrndr.extra.dnk3.cubemap.glslFetchSH
|
||||
import org.openrndr.extra.dnk3.cubemap.genGlslGatherSH
|
||||
import org.openrndr.extra.shaderphrases.phrases.phraseTbnMatrix
|
||||
import org.openrndr.math.Vector2
|
||||
import org.openrndr.math.Vector3
|
||||
import org.openrndr.math.Vector4
|
||||
import org.openrndr.math.transforms.normalMatrix
|
||||
import java.nio.ByteBuffer
|
||||
import javax.naming.Context
|
||||
import kotlin.math.cos
|
||||
|
||||
|
||||
private val noise128 by lazy {
|
||||
val cb = colorBuffer(128, 128)
|
||||
val items = cb.width * cb.height * cb.format.componentCount
|
||||
@@ -123,7 +125,11 @@ object DummySource : TextureSource() {
|
||||
|
||||
abstract class TextureFromColorBuffer(var texture: ColorBuffer, var textureFunction: TextureFunction) : TextureSource()
|
||||
|
||||
class TextureFromCode(val code: String) : TextureSource()
|
||||
class TextureFromCode(val code: String) : TextureSource() {
|
||||
override fun hashCode(): Int {
|
||||
return code.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
private fun TextureFromCode.fs(index: Int, target: TextureTarget) = """
|
||||
|vec4 tex$index = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
@@ -137,6 +143,7 @@ private fun TextureFromCode.fs(index: Int, target: TextureTarget) = """
|
||||
enum class TextureFunction(val function: (String, String) -> String) {
|
||||
TILING({ texture, uv -> "texture($texture, $uv)" }),
|
||||
NOT_TILING({ texture, uv -> "textureNoTile(p_textureNoise, $texture, x_noTileOffset, $uv)" })
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,8 +162,17 @@ class ModelCoordinates(texture: ColorBuffer,
|
||||
override fun toString(): String {
|
||||
return "ModelCoordinates(texture: $texture, input: $input, $tangentInput: $tangentInput, textureFunction: $textureFunction, pre: $pre, post: $post)"
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = input.hashCode()
|
||||
result = 31 * result + (tangentInput?.hashCode() ?: 0)
|
||||
result = 31 * result + (pre?.hashCode() ?: 0)
|
||||
result = 31 * result + (post?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Triplanar(texture: ColorBuffer,
|
||||
var scale: Double = 1.0,
|
||||
var offset: Vector3 = Vector3.ZERO,
|
||||
@@ -170,6 +186,17 @@ class Triplanar(texture: ColorBuffer,
|
||||
texture.wrapU = WrapMode.REPEAT
|
||||
texture.wrapV = WrapMode.REPEAT
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = scale.hashCode()
|
||||
result = 31 * result + offset.hashCode()
|
||||
result = 31 * result + sharpness.hashCode()
|
||||
result = 31 * result + (pre?.hashCode() ?: 0)
|
||||
result = 31 * result + (post?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun ModelCoordinates.fs(index: Int) = """
|
||||
@@ -187,8 +214,7 @@ private fun ModelCoordinates.fs(index: Int) = """
|
||||
| vec3 tangent = normalize(${tangentInput}.xyz);
|
||||
| vec3 bitangent = cross(normal, tangent) * ${tangentInput}.w;
|
||||
| mat3 tbn = mat3(tangent, bitangent, normal);
|
||||
| x_texture.rgb = tbn * normalize(x_texture.rgb - vec3(0.5, 0.5, 0.)) ;
|
||||
|
|
||||
| x_texture.rgb = tbn * normalize( (x_texture.rgb - vec3(0.5, 0.5, 0.0))*vec3(2.0, 2.0, 1.0)) ;
|
||||
""".trimMargin()
|
||||
|
||||
} else ""}
|
||||
@@ -250,6 +276,10 @@ sealed class TextureTarget(val name: String) {
|
||||
override fun toString(): String {
|
||||
return "TextureTarget(name: $name)"
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return name.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
class Texture(var source: TextureSource,
|
||||
@@ -262,18 +292,63 @@ class Texture(var source: TextureSource,
|
||||
override fun toString(): String {
|
||||
return "Texture(source: $source, target: $target)"
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = source.hashCode()
|
||||
result = 31 * result + target.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private var fragmentIDCounter = 1
|
||||
|
||||
data class SubsurfaceScatter(var enabled: Boolean) {
|
||||
var color: ColorRGBa = ColorRGBa.WHITE
|
||||
var shape = 1.0
|
||||
|
||||
fun fs(): String {
|
||||
return if (enabled) """
|
||||
f_diffuse.rgb += pow(smoothstep(1.0, 0.0, abs(dot(normalize(N),normalize(V)))), p_sssShape) * clamp(evaluateSH(-V, sh), vec3(0.0), vec3(1.0)) * p_sssColor.rgb;
|
||||
""" else ""
|
||||
}
|
||||
|
||||
fun applyToShadeStyle(shadeStyle: ShadeStyle) {
|
||||
if (enabled) {
|
||||
shadeStyle.parameter("sssColor", color)
|
||||
shadeStyle.parameter("sssShape", shape)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class CubemapReflection(var cubemap: Cubemap? = null) {
|
||||
var color: ColorRGBa = ColorRGBa.WHITE
|
||||
|
||||
fun fs(): String {
|
||||
return if (cubemap != null) {
|
||||
"""
|
||||
vec2 dfg = PrefilteredDFG_Karis(m_roughness, NoV);
|
||||
vec3 sc = m_metalness * m_color.rgb + (1.0-m_metalness) * vec3(0.04);
|
||||
f_specular.rgb += sc * (texture(p_radianceMap, reflect(-V, normalize(f_worldNormal)), m_roughness*7.0 ).rgb * dfg.x + dfg.y) * p_radianceColor.rgb;
|
||||
"""
|
||||
} else { "" }
|
||||
}
|
||||
fun applyToShadeStyle(shadeStyle: ShadeStyle) {
|
||||
if (cubemap != null) {
|
||||
shadeStyle.parameter("radianceMap", cubemap!!)
|
||||
shadeStyle.parameter("radianceColor", color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class PBRMaterial : Material {
|
||||
override var name: String? = null
|
||||
override fun toString(): String {
|
||||
return "PBRMaterial(textures: $textures, color: $color, metalness: $metalness, roughness: $roughness, emissive: $emission))"
|
||||
return "PBRMaterial(name: $name, fragmentID: $fragmentID, doubleSided: $doubleSided, textures: $textures, color: $color, metalness: $metalness, roughness: $roughness, emissive: $emission))"
|
||||
}
|
||||
|
||||
override var fragmentID = fragmentIDCounter.apply {
|
||||
fragmentIDCounter++
|
||||
|
||||
}
|
||||
|
||||
override var doubleSided: Boolean = false
|
||||
@@ -284,6 +359,10 @@ class PBRMaterial : Material {
|
||||
var roughness = 1.0
|
||||
var emission = ColorRGBa.BLACK
|
||||
|
||||
var subsurfaceScatter = SubsurfaceScatter(false)
|
||||
var cubemapReflection = CubemapReflection(null)
|
||||
|
||||
|
||||
var fragmentPreamble: String? = null
|
||||
var vertexPreamble: String? = null
|
||||
var vertexTransform: String? = null
|
||||
@@ -292,21 +371,6 @@ class PBRMaterial : Material {
|
||||
|
||||
val shadeStyles = mutableMapOf<ContextKey, ShadeStyle>()
|
||||
|
||||
// fun copy(): PBRMaterial {
|
||||
// val copied = PBRMaterial()
|
||||
// copied.environmentMap = environmentMap
|
||||
// copied.color = color
|
||||
// copied.opacity = opacity
|
||||
// copied.metalness = metalness
|
||||
// copied.roughness = roughness
|
||||
// copied.emission = emission
|
||||
// copied.vertexPreamble = vertexPreamble
|
||||
// copied.vertexTransform = vertexTransform
|
||||
// copied.parameters.putAll(parameters)
|
||||
// copied.textures.addAll(textures.map { it.copy() })
|
||||
// return copied
|
||||
// }
|
||||
|
||||
override fun generateShadeStyle(materialContext: MaterialContext, primitiveContext: PrimitiveContext): ShadeStyle {
|
||||
val cached = shadeStyles.getOrPut(ContextKey(materialContext, primitiveContext)) {
|
||||
val needLight = needLight(materialContext)
|
||||
@@ -321,6 +385,7 @@ class PBRMaterial : Material {
|
||||
vec3 m_normal = vec3(0.0, 0.0, 1.0);
|
||||
vec4 f_fog = vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec3 f_worldNormal = v_worldNormal;
|
||||
vec3 f_emission = m_emission;
|
||||
""".trimIndent()
|
||||
|
||||
val textureFs = if (needLight) {
|
||||
@@ -346,7 +411,6 @@ class PBRMaterial : Material {
|
||||
}
|
||||
}).joinToString("\n")
|
||||
} else ""
|
||||
|
||||
val displacers = textures.filter { it.target is TextureTarget.Height }
|
||||
|
||||
val skinVS = if (primitiveContext.hasSkinning) """
|
||||
@@ -377,23 +441,32 @@ class PBRMaterial : Material {
|
||||
}.joinToString("\n") else ""
|
||||
|
||||
val lights = materialContext.lights
|
||||
|
||||
val doubleSidedFS = if (doubleSided) {
|
||||
"""
|
||||
if (dot(V, N) <0) {
|
||||
N *= -1.0;
|
||||
}
|
||||
""".trimIndent()
|
||||
} else ""
|
||||
val lightFS = if (needLight) """
|
||||
vec3 f_diffuse = vec3(0.0);
|
||||
vec3 f_specular = vec3(0.0);
|
||||
vec3 f_emission = m_emission;
|
||||
vec3 f_ambient = vec3(0.0);
|
||||
float f_occlusion = 1.0;
|
||||
vec3 N = normalize(f_worldNormal);
|
||||
|
||||
vec3 ep = (p_viewMatrixInverse * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
|
||||
vec3 Vr = ep - v_worldPosition;
|
||||
vec3 V = normalize(Vr);
|
||||
|
||||
float NoV = ${if (primitiveContext.hasNormalAttribute) "abs(dot(N, V)) + 1e-5" else "1"};
|
||||
|
||||
${if (environmentMap && materialContext.meshCubemaps.isNotEmpty() && primitiveContext.hasNormalAttribute) """
|
||||
{
|
||||
vec2 dfg = PrefilteredDFG_Karis(m_roughness, NoV);
|
||||
vec3 sc = m_metalness * m_color.rgb + (1.0-m_metalness) * vec3(0.04);
|
||||
|
||||
|
||||
f_specular.rgb += sc * (texture(p_environmentMap, reflect(-V, normalize(f_worldNormal))).rgb * dfg.x + dfg.y) * m_ambientOcclusion;
|
||||
}
|
||||
""".trimIndent() else ""}
|
||||
@@ -409,7 +482,21 @@ class PBRMaterial : Material {
|
||||
}
|
||||
}.joinToString("\n")}
|
||||
|
||||
|
||||
${if (materialContext.irradianceSH?.shMap != null) """
|
||||
vec3[9] sh;
|
||||
gatherSH(p_shMap, v_worldPosition, sh);
|
||||
vec3 irradiance = clamp(evaluateSH(normalize(N), sh), vec3(0.0), vec3(1.0)) * m_color.rgb;
|
||||
vec3 ks = F_SchlickRoughness(m_color.rgb * (m_metalness) + 0.04 * (1.0-m_metalness), m_roughness+0.1, min(NoV, 1.0-1.0e-6));
|
||||
f_diffuse.rgb = irradiance * ks;
|
||||
f_ambient.rgb = (1.0-ks) * irradiance;
|
||||
${subsurfaceScatter.fs()}
|
||||
${cubemapReflection.fs()}
|
||||
""".trimIndent() else ""
|
||||
}
|
||||
|
||||
${materialContext.fogs.mapIndexed { index, (node, fog) ->
|
||||
|
||||
fog.fs(index)
|
||||
}.joinToString("\n")}
|
||||
|
||||
@@ -430,24 +517,36 @@ class PBRMaterial : Material {
|
||||
${(this@PBRMaterial.vertexPreamble) ?: ""}
|
||||
""".trimIndent()
|
||||
fragmentPreamble += """
|
||||
${if (materialContext.irradianceSH?.shMap != null) {
|
||||
"""
|
||||
$glslEvaluateSH
|
||||
$glslFetchSH
|
||||
${genGlslGatherSH(materialContext.irradianceSH!!.xCount, materialContext.irradianceSH!!.yCount,
|
||||
materialContext.irradianceSH!!.zCount, materialContext.irradianceSH!!.spacing, materialContext.irradianceSH!!.offset)}
|
||||
"""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|$shaderLinePlaneIntersect
|
||||
|$shaderProjectOnPlane
|
||||
|$shaderSideOfPlane
|
||||
|$shaderGGX
|
||||
|$shaderVSM
|
||||
|$shaderNoRepetition
|
||||
|$phraseTbnMatrix
|
||||
""".trimMargin()
|
||||
this.suppressDefaultOutput = true
|
||||
this.vertexTransform = vs
|
||||
fragmentTransform = fs
|
||||
|
||||
materialContext.pass.combiners.map {
|
||||
if (rt is ProgramRenderTarget || materialContext.pass === DefaultPass || materialContext.pass === DefaultOpaquePass || materialContext.pass == DefaultTransparentPass) {
|
||||
if (rt is ProgramRenderTarget || materialContext.pass === DefaultPass || materialContext.pass === DefaultOpaquePass || materialContext.pass == DefaultTransparentPass || materialContext.pass == IrradianceProbePass) {
|
||||
this.output(it.targetOutput, ShadeStyleOutput(0))
|
||||
} else {
|
||||
val index = rt.colorAttachmentIndexByName(it.targetOutput) ?: error("no such attachment ${it.targetOutput}")
|
||||
val index = rt.colorAttachmentIndexByName(it.targetOutput)?:error("attachment ${it.targetOutput} not found")
|
||||
val type = rt.colorBuffer(index).type
|
||||
val format = rt.colorBuffer(0).format
|
||||
val format = rt.colorBuffer(index).format
|
||||
this.output(it.targetOutput, ShadeStyleOutput(index, format, type))
|
||||
}
|
||||
}
|
||||
@@ -470,6 +569,10 @@ class PBRMaterial : Material {
|
||||
shadeStyle.parameter("roughness", roughness)
|
||||
shadeStyle.parameter("fragmentID", fragmentID)
|
||||
|
||||
if (context.irradianceProbeCount > 0) {
|
||||
shadeStyle.parameter("shMap", context.irradianceSH?.shMap!!)
|
||||
}
|
||||
|
||||
parameters.forEach { (k, v) ->
|
||||
when (v) {
|
||||
is Double -> shadeStyle.parameter(k, v)
|
||||
@@ -483,6 +586,10 @@ class PBRMaterial : Material {
|
||||
}
|
||||
}
|
||||
if (needLight(context)) {
|
||||
|
||||
subsurfaceScatter.applyToShadeStyle(shadeStyle)
|
||||
cubemapReflection.applyToShadeStyle(shadeStyle)
|
||||
|
||||
textures.forEachIndexed { index, texture ->
|
||||
when (val source = texture.source) {
|
||||
is TextureFromColorBuffer -> {
|
||||
@@ -598,5 +705,23 @@ class PBRMaterial : Material {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = fragmentID.hashCode()
|
||||
result = 31 * doubleSided.hashCode()
|
||||
result = 31 * result + transparent.hashCode()
|
||||
// result = 31 * result + environmentMap.hashCode()
|
||||
result = 31 * result + color.hashCode()
|
||||
result = 31 * result + metalness.hashCode()
|
||||
result = 31 * result + roughness.hashCode()
|
||||
result = 31 * result + emission.hashCode()
|
||||
result = 31 * result + (fragmentPreamble?.hashCode() ?: 0)
|
||||
result = 31 * result + (vertexPreamble?.hashCode() ?: 0)
|
||||
result = 31 * result + (vertexTransform?.hashCode() ?: 0)
|
||||
// result = 31 * result + parameters.hashCode()
|
||||
// result = 31 * result + textures.hashCode()
|
||||
// result = 31 * result + shadeStyles.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,16 @@ interface PostStep {
|
||||
fun apply(buffers: MutableMap<String, ColorBuffer>, postContext: PostContext)
|
||||
}
|
||||
|
||||
class FilterPostStep(val outputScale: Double,
|
||||
val filter: Filter,
|
||||
class FilterPostStep<T:Filter>(val outputScale: Double,
|
||||
val filter: T,
|
||||
val inputs: List<String>,
|
||||
val output: String,
|
||||
val outputFormat: ColorFormat,
|
||||
val outputType: ColorType,
|
||||
val update: (Filter.(PostContext) -> Unit)? = null) : PostStep {
|
||||
val update: (T.(PostContext) -> Unit)? = null) : PostStep {
|
||||
|
||||
override fun apply(buffers: MutableMap<String, ColorBuffer>, postContext: PostContext) {
|
||||
val inputBuffers = inputs.map { buffers[it]!! }
|
||||
val inputBuffers = inputs.map { buffers[it]?: error("buffer not found: $it") }
|
||||
val outputBuffer = buffers.getOrPut(output) {
|
||||
colorBuffer((inputBuffers[0].width * outputScale).toInt(),
|
||||
(inputBuffers[0].height * outputScale).toInt(),
|
||||
|
||||
@@ -13,6 +13,8 @@ data class RenderPass(val combiners: List<FacetCombiner>,
|
||||
|
||||
|
||||
val DefaultPass = RenderPass(listOf(LDRColorFacet()))
|
||||
val IrradianceProbePass = RenderPass(listOf(DiffuseIrradianceFacet()))
|
||||
|
||||
val DefaultOpaquePass = RenderPass(listOf(LDRColorFacet()), renderOpaque = true, renderTransparent = false)
|
||||
val DefaultTransparentPass = RenderPass(listOf(LDRColorFacet()), renderOpaque = false, renderTransparent = true, depthWrite = false)
|
||||
val LightPass = RenderPass(emptyList())
|
||||
|
||||
@@ -2,11 +2,21 @@ package org.openrndr.extra.dnk3
|
||||
|
||||
import org.openrndr.Dispatcher
|
||||
import org.openrndr.math.Matrix44
|
||||
import org.openrndr.math.Vector3
|
||||
import org.openrndr.math.Vector4
|
||||
import java.util.*
|
||||
|
||||
class Scene(val root: SceneNode = SceneNode(), val dispatcher: Dispatcher = Dispatcher())
|
||||
class Scene(val root: SceneNode = SceneNode(), val dispatcher: Dispatcher = Dispatcher()) {
|
||||
val features = mutableListOf<Feature>()
|
||||
override fun hashCode(): Int {
|
||||
var result = root.hashCode()
|
||||
result = result * 31 + features.hashCode()
|
||||
return result
|
||||
}
|
||||
fun hash(): String = Base64.getEncoder().encodeToString(hashCode().toString().toByteArray())
|
||||
}
|
||||
|
||||
|
||||
open class SceneNode() {
|
||||
open class SceneNode {
|
||||
var name: String = ""
|
||||
var entities: MutableList<Entity> = mutableListOf()
|
||||
var parent: SceneNode? = null
|
||||
@@ -14,8 +24,24 @@ open class SceneNode() {
|
||||
var worldTransform = Matrix44.IDENTITY
|
||||
val children = mutableListOf<SceneNode>()
|
||||
var disposed = false
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name.hashCode()
|
||||
result = 31 * result + entities.hashCode()
|
||||
// result = 31 * result + (parent?.hashCode() ?: 0)
|
||||
result = 31 * result + transform.hashCode()
|
||||
result = 31 * result + worldTransform.hashCode()
|
||||
result = 31 * result + children.hashCode()
|
||||
result = 31 * result + disposed.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
val SceneNode.worldPosition: Vector3
|
||||
get() {
|
||||
return (worldTransform * Vector4.UNIT_W).xyz
|
||||
}
|
||||
|
||||
class NodeContent<T>(val node: SceneNode, val content: T) {
|
||||
operator fun component1() = node
|
||||
operator fun component2() = content
|
||||
|
||||
@@ -2,10 +2,19 @@ package org.openrndr.extra.dnk3
|
||||
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.draw.depthBuffer
|
||||
import org.openrndr.extra.dnk3.features.IrradianceSH
|
||||
import org.openrndr.extra.fx.blur.ApproximateGaussianBlur
|
||||
import org.openrndr.math.Matrix44
|
||||
import org.openrndr.math.transforms.normalMatrix
|
||||
import org.openrndr.math.Vector3
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
class RenderContext(
|
||||
val lights: List<NodeContent<Light>>,
|
||||
val meshes: List<NodeContent<Mesh>>,
|
||||
val skinnedMeshes: List<NodeContent<SkinnedMesh>>,
|
||||
val instancedMeshes: List<NodeContent<InstancedMesh>>,
|
||||
val fogs: List<NodeContent<Fog>>
|
||||
)
|
||||
|
||||
class SceneRenderer {
|
||||
|
||||
@@ -20,7 +29,6 @@ class SceneRenderer {
|
||||
var shadowLightTargets = mutableMapOf<ShadowLight, RenderTarget>()
|
||||
var meshCubemaps = mutableMapOf<Mesh, Cubemap>()
|
||||
|
||||
var cubemapDepthBuffer = depthBuffer(256, 256, DepthFormat.DEPTH16, BufferMultisample.Disabled)
|
||||
|
||||
var outputPasses = mutableListOf(DefaultOpaquePass, DefaultTransparentPass)
|
||||
var outputPassTarget: RenderTarget? = null
|
||||
@@ -31,6 +39,7 @@ class SceneRenderer {
|
||||
|
||||
var drawFinalBuffer = true
|
||||
|
||||
var first = true
|
||||
fun draw(drawer: Drawer, scene: Scene) {
|
||||
drawer.pushStyle()
|
||||
drawer.depthWrite = true
|
||||
@@ -40,22 +49,27 @@ class SceneRenderer {
|
||||
|
||||
scene.dispatcher.execute()
|
||||
|
||||
|
||||
// update all the transforms
|
||||
scene.root.scan(Matrix44.IDENTITY) { p ->
|
||||
worldTransform = p * transform
|
||||
if (p !== Matrix44.IDENTITY) {
|
||||
worldTransform = p * transform
|
||||
} else {
|
||||
worldTransform = transform
|
||||
}
|
||||
worldTransform
|
||||
}
|
||||
|
||||
val lights = scene.root.findContent { this as? Light }
|
||||
val meshes = scene.root.findContent { this as? Mesh }
|
||||
val skinnedMeshes = scene.root.findContent { this as? SkinnedMesh }
|
||||
|
||||
val fogs = scene.root.findContent { this as? Fog }
|
||||
val instancedMeshes = scene.root.findContent { this as? InstancedMesh }
|
||||
val context = RenderContext(
|
||||
lights = scene.root.findContent { this as? Light },
|
||||
meshes = scene.root.findContent { this as? Mesh },
|
||||
skinnedMeshes = scene.root.findContent { this as? SkinnedMesh },
|
||||
fogs = scene.root.findContent { this as? Fog },
|
||||
instancedMeshes = scene.root.findContent { this as? InstancedMesh }
|
||||
)
|
||||
|
||||
// shadow passes
|
||||
run {
|
||||
lights.filter { it.content is ShadowLight && (it.content as ShadowLight).shadows is Shadows.MappedShadows }.forEach {
|
||||
context.lights.filter { it.content is ShadowLight && (it.content as ShadowLight).shadows is Shadows.MappedShadows }.forEach {
|
||||
val shadowLight = it.content as ShadowLight
|
||||
val pass: RenderPass
|
||||
pass = when (shadowLight.shadows) {
|
||||
@@ -74,7 +88,7 @@ class SceneRenderer {
|
||||
target.clearDepth(depth = 1.0)
|
||||
|
||||
val look = shadowLight.view(it.node)
|
||||
val materialContext = MaterialContext(pass, lights, fogs, shadowLightTargets, emptyMap())
|
||||
val materialContext = MaterialContext(pass, context.lights, context.fogs, shadowLightTargets, emptyMap(), 0)
|
||||
drawer.isolatedWithTarget(target) {
|
||||
drawer.projection = shadowLight.projection(target)
|
||||
drawer.view = look
|
||||
@@ -82,7 +96,7 @@ class SceneRenderer {
|
||||
|
||||
drawer.clear(ColorRGBa.BLACK)
|
||||
drawer.cullTestPass = CullTestPass.FRONT
|
||||
drawPass(drawer, pass, materialContext, meshes, instancedMeshes, skinnedMeshes)
|
||||
drawPass(drawer, pass, materialContext, context)
|
||||
}
|
||||
when (shadowLight.shadows) {
|
||||
is Shadows.VSM -> {
|
||||
@@ -96,10 +110,18 @@ class SceneRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// -- feature passes
|
||||
for (feature in scene.features) {
|
||||
feature.update(drawer, this, scene, feature, context)
|
||||
}
|
||||
|
||||
// -- output passes
|
||||
run {
|
||||
//val pass = outputPasses
|
||||
val irradianceSH = scene.features.find { it is IrradianceSH } as? IrradianceSH
|
||||
for (pass in outputPasses) {
|
||||
val materialContext = MaterialContext(pass, lights, fogs, shadowLightTargets, meshCubemaps)
|
||||
val materialContext = MaterialContext(pass, context.lights, context.fogs, shadowLightTargets, meshCubemaps, irradianceSH?.probeCount
|
||||
?: 0)
|
||||
materialContext.irradianceSH = irradianceSH
|
||||
|
||||
val defaultPasses = setOf(DefaultTransparentPass, DefaultOpaquePass)
|
||||
|
||||
@@ -118,23 +140,23 @@ class SceneRenderer {
|
||||
pass.combiners.forEach {
|
||||
if (it is ColorBufferFacetCombiner) {
|
||||
val index = target.colorAttachmentIndexByName(it.targetOutput)
|
||||
?: error("no such attachment ${it.targetOutput}")
|
||||
?: error("attachement not found ${it.targetOutput}")
|
||||
target.blendMode(index, it.blendMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
outputPassTarget?.bind()
|
||||
drawPass(drawer, pass, materialContext, meshes, instancedMeshes, skinnedMeshes)
|
||||
drawPass(drawer, pass, materialContext, context)
|
||||
outputPassTarget?.unbind()
|
||||
|
||||
outputPassTarget?.let { output ->
|
||||
for (combiner in pass.combiners) {
|
||||
buffers[combiner.targetOutput] = (output.colorAttachmentByName(combiner.targetOutput) as? ColorBufferAttachment)?.colorBuffer
|
||||
?: error("no such attachment: ${combiner.targetOutput}")
|
||||
?: error("attachment not found ${combiner.targetOutput}")
|
||||
}
|
||||
}
|
||||
}
|
||||
val lightContext = LightContext(lights, shadowLightTargets)
|
||||
val lightContext = LightContext(context.lights, shadowLightTargets)
|
||||
val postContext = PostContext(lightContext, drawer.view.inversed)
|
||||
|
||||
for (postStep in postSteps) {
|
||||
@@ -146,10 +168,9 @@ class SceneRenderer {
|
||||
if (drawFinalBuffer) {
|
||||
outputPassTarget?.let { output ->
|
||||
drawer.isolated {
|
||||
drawer.defaults()
|
||||
drawer.ortho()
|
||||
drawer.view = Matrix44.IDENTITY
|
||||
drawer.model = Matrix44.IDENTITY
|
||||
val outputName = (postSteps.last() as FilterPostStep).output
|
||||
val outputName = (postSteps.lastOrNull() as? FilterPostStep<*>)?.output ?: "color"
|
||||
val outputBuffer = buffers[outputName]
|
||||
?: throw IllegalArgumentException("can't find $outputName buffer")
|
||||
drawer.image(outputBuffer)
|
||||
@@ -158,14 +179,12 @@ class SceneRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private fun drawPass(drawer: Drawer, pass: RenderPass, materialContext: MaterialContext,
|
||||
meshes: List<NodeContent<Mesh>>,
|
||||
instancedMeshes: List<NodeContent<InstancedMesh>>,
|
||||
skinnedMeshes: List<NodeContent<SkinnedMesh>>
|
||||
internal fun drawPass(drawer: Drawer, pass: RenderPass, materialContext: MaterialContext,
|
||||
context: RenderContext
|
||||
) {
|
||||
|
||||
drawer.depthWrite = pass.depthWrite
|
||||
val primitives = meshes.flatMap { mesh ->
|
||||
val primitives = context.meshes.flatMap { mesh ->
|
||||
mesh.content.primitives.map { primitive ->
|
||||
NodeContent(mesh.node, primitive)
|
||||
}
|
||||
@@ -204,7 +223,7 @@ class SceneRenderer {
|
||||
}
|
||||
|
||||
|
||||
val skinnedPrimitives = skinnedMeshes.flatMap { mesh ->
|
||||
val skinnedPrimitives = context.skinnedMeshes.flatMap { mesh ->
|
||||
mesh.content.primitives.map { primitive ->
|
||||
NodeContent(mesh.node, Pair(primitive, mesh))
|
||||
}
|
||||
@@ -230,12 +249,9 @@ class SceneRenderer {
|
||||
|
||||
val jointTransforms = (skinnedMesh.joints zip skinnedMesh.inverseBindMatrices)
|
||||
.map { (nodeInverse * it.first.worldTransform * it.second) }
|
||||
// val jointNormalTransforms = jointTransforms.map { Matrix44.IDENTITY }
|
||||
|
||||
val shadeStyle = primitive.material.generateShadeStyle(materialContext, primitiveContext)
|
||||
|
||||
shadeStyle.parameter("jointTransforms", jointTransforms.toTypedArray())
|
||||
// shadeStyle.parameter("jointNormalTransforms", jointNormalTransforms.toTypedArray())
|
||||
|
||||
shadeStyle.parameter("viewMatrixInverse", drawer.view.inversed)
|
||||
primitive.material.applyToShadeStyle(materialContext, shadeStyle)
|
||||
@@ -258,7 +274,7 @@ class SceneRenderer {
|
||||
}
|
||||
|
||||
|
||||
val instancedPrimitives = instancedMeshes.flatMap { mesh ->
|
||||
val instancedPrimitives = context.instancedMeshes.flatMap { mesh ->
|
||||
mesh.content.primitives.map { primitive ->
|
||||
NodeContent(mesh.node, MeshPrimitiveInstance(primitive, mesh.content.instances, mesh.content.attributes))
|
||||
}
|
||||
@@ -295,4 +311,10 @@ fun sceneRenderer(builder: SceneRenderer.() -> Unit): SceneRenderer {
|
||||
val sceneRenderer = SceneRenderer()
|
||||
sceneRenderer.builder()
|
||||
return sceneRenderer
|
||||
}
|
||||
|
||||
internal fun ByteBuffer.putVector3(v: Vector3) {
|
||||
putFloat(v.x.toFloat())
|
||||
putFloat(v.y.toFloat())
|
||||
putFloat(v.z.toFloat())
|
||||
}
|
||||
@@ -167,6 +167,10 @@ vec3 F_Schlick(const vec3 f0, float VoH) {
|
||||
// Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"
|
||||
return f0 + (vec3(1.0) - f0) * pow5(1.0 - VoH);
|
||||
}
|
||||
vec3 F_SchlickRoughness(vec3 F0, float roughness, float VoH)
|
||||
{
|
||||
return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1.0 - VoH, 5.0);
|
||||
}
|
||||
|
||||
float F_Schlick(float f0, float f90, float VoH) {
|
||||
return f0 + (f90 - f0) * pow5(1.0 - VoH);
|
||||
|
||||
187
orx-dnk3/src/main/kotlin/cubemap/CubemapFilter.kt
Normal file
187
orx-dnk3/src/main/kotlin/cubemap/CubemapFilter.kt
Normal file
@@ -0,0 +1,187 @@
|
||||
package org.openrndr.extra.dnk3.cubemap
|
||||
|
||||
import org.openrndr.draw.*
|
||||
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.internal.Driver
|
||||
import org.openrndr.math.*
|
||||
import org.openrndr.math.transforms.ortho
|
||||
|
||||
private val filterDrawStyle = DrawStyle().apply {
|
||||
blendMode = BlendMode.REPLACE
|
||||
depthWrite = false
|
||||
depthTestPass = DepthTestPass.ALWAYS
|
||||
stencil.stencilTest = StencilTest.DISABLED
|
||||
}
|
||||
|
||||
private var filterQuad: VertexBuffer? = null
|
||||
private var filterQuadFormat = vertexFormat {
|
||||
position(2)
|
||||
textureCoordinate(2)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter base class. Renders "full-screen" quads.
|
||||
*/
|
||||
open class CubemapFilter(private val shader: Shader? = null, private val watcher: ShaderWatcher? = null) {
|
||||
|
||||
/**
|
||||
* parameter map
|
||||
*/
|
||||
val parameters = mutableMapOf<String, Any>()
|
||||
var padding = 0
|
||||
|
||||
var depthBufferOut: DepthBuffer? = null
|
||||
|
||||
companion object {
|
||||
val filterVertexCode: String get() = Driver.instance.internalShaderResource("filter.vert")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
open fun apply(source: Array<Cubemap>, target: Array<Cubemap>) {
|
||||
if (target.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
for (side in CubemapSide.values()) {
|
||||
val renderTarget = renderTarget(target[0].width, target[0].width, 1.0) {}
|
||||
|
||||
shader?.begin()
|
||||
shader?.uniform("sideNormal", side.forward)
|
||||
shader?.uniform("sideUp", side.up)
|
||||
shader?.uniform("sideRight", (side.forward cross side.up))
|
||||
shader?.end()
|
||||
|
||||
|
||||
target.forEach {
|
||||
renderTarget.attach(it, side, 0)
|
||||
}
|
||||
|
||||
for (i in 1 until target.size) {
|
||||
renderTarget.blendMode(i, BlendMode.REPLACE)
|
||||
}
|
||||
|
||||
apply(source, renderTarget)
|
||||
depthBufferOut?.let {
|
||||
renderTarget.attach(it)
|
||||
}
|
||||
|
||||
if (depthBufferOut != null) {
|
||||
renderTarget.detachDepthBuffer()
|
||||
}
|
||||
|
||||
renderTarget.detachColorBuffers()
|
||||
renderTarget.destroy()
|
||||
}
|
||||
}
|
||||
|
||||
fun apply(source: Array<Cubemap>, target: RenderTarget) {
|
||||
val shader = if (this.watcher != null) watcher.shader!! else this.shader!!
|
||||
target.bind()
|
||||
|
||||
if (filterQuad == null) {
|
||||
val fq = VertexBuffer.createDynamic(filterQuadFormat, 6, Session.root)
|
||||
|
||||
fq.shadow.writer().apply {
|
||||
write(Vector2(0.0, 1.0)); write(Vector2(0.0, 0.0))
|
||||
write(Vector2(0.0, 0.0)); write(Vector2(0.0, 1.0))
|
||||
write(Vector2(1.0, 0.0)); write(Vector2(1.0, 1.0))
|
||||
|
||||
write(Vector2(0.0, 1.0)); write(Vector2(0.0, 0.0))
|
||||
write(Vector2(1.0, 1.0)); write(Vector2(1.0, 0.0))
|
||||
write(Vector2(1.0, 0.0)); write(Vector2(1.0, 1.0))
|
||||
}
|
||||
fq.shadow.upload()
|
||||
fq.shadow.destroy()
|
||||
filterQuad = fq
|
||||
}
|
||||
|
||||
shader.begin()
|
||||
|
||||
source.forEachIndexed { index, cubemap ->
|
||||
cubemap.bind(index)
|
||||
cubemap.filter(MinifyingFilter.LINEAR, MagnifyingFilter.LINEAR)
|
||||
shader.uniform("tex$index", index)
|
||||
}
|
||||
|
||||
Driver.instance.setState(filterDrawStyle)
|
||||
|
||||
shader.uniform("projectionMatrix", ortho(0.0, target.width.toDouble(), target.height.toDouble(), 0.0, -1.0, 1.0))
|
||||
shader.uniform("targetSize", Vector2(target.width.toDouble(), target.height.toDouble()))
|
||||
shader.uniform("padding", Vector2(padding.toDouble(), padding.toDouble()))
|
||||
|
||||
var textureIndex = source.size + 0
|
||||
parameters.forEach { (uniform, value) ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
when (value) {
|
||||
is Boolean -> shader.uniform(uniform, value)
|
||||
is Float -> shader.uniform(uniform, value)
|
||||
is Double -> shader.uniform(uniform, value.toFloat())
|
||||
is Matrix44 -> shader.uniform(uniform, value)
|
||||
is Vector2 -> shader.uniform(uniform, value)
|
||||
is Vector3 -> shader.uniform(uniform, value)
|
||||
is Vector4 -> shader.uniform(uniform, value)
|
||||
is ColorRGBa -> shader.uniform(uniform, value)
|
||||
is Int -> shader.uniform(uniform, value)
|
||||
is Matrix55 -> shader.uniform(uniform, value.floatArray)
|
||||
is FloatArray -> shader.uniform(uniform, value)
|
||||
|
||||
// EJ: this is not so nice but I have no other ideas for this
|
||||
is Array<*> -> if (value.size > 0) when (value[0]) {
|
||||
is Vector2 -> shader.uniform(uniform, value as Array<Vector2>)
|
||||
is Vector3 -> shader.uniform(uniform, value as Array<Vector3>)
|
||||
is Vector4 -> shader.uniform(uniform, value as Array<Vector4>)
|
||||
else -> throw IllegalArgumentException("unsupported array value: ${value[0]!!::class.java}")
|
||||
//is ColorRGBa -> shader.uniform(uniform, value as Array<ColorRGBa>)
|
||||
}
|
||||
|
||||
is DepthBuffer -> {
|
||||
shader.uniform("$uniform", textureIndex)
|
||||
value.bind(textureIndex)
|
||||
textureIndex++
|
||||
}
|
||||
|
||||
is ColorBuffer -> {
|
||||
shader.uniform("$uniform", textureIndex)
|
||||
value.bind(textureIndex)
|
||||
textureIndex++
|
||||
}
|
||||
|
||||
is Cubemap -> {
|
||||
shader.uniform("$uniform", textureIndex)
|
||||
value.bind(textureIndex)
|
||||
textureIndex++
|
||||
}
|
||||
|
||||
is ArrayTexture -> {
|
||||
shader.uniform("$uniform", textureIndex)
|
||||
value.bind(textureIndex)
|
||||
textureIndex++
|
||||
}
|
||||
|
||||
is BufferTexture -> {
|
||||
shader.uniform("$uniform", textureIndex)
|
||||
value.bind(textureIndex)
|
||||
textureIndex++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Driver.instance.drawVertexBuffer(shader, listOf(filterQuad!!), DrawPrimitive.TRIANGLES, 0, 6)
|
||||
shader.end()
|
||||
target.unbind()
|
||||
}
|
||||
|
||||
fun apply(source: Cubemap, target: Cubemap) = apply(arrayOf(source), arrayOf(target))
|
||||
fun apply(source: Cubemap, target: Array<Cubemap>) = apply(arrayOf(source), target)
|
||||
fun apply(source: Array<Cubemap>, target: Cubemap) = apply(source, arrayOf(target))
|
||||
|
||||
fun untrack() {
|
||||
shader?.let { Session.active.untrack(shader) }
|
||||
}
|
||||
|
||||
protected val format get() = filterQuadFormat
|
||||
}
|
||||
6
orx-dnk3/src/main/kotlin/cubemap/CubemapPassthrough.kt
Normal file
6
orx-dnk3/src/main/kotlin/cubemap/CubemapPassthrough.kt
Normal file
@@ -0,0 +1,6 @@
|
||||
package org.openrndr.extra.dnk3.cubemap
|
||||
|
||||
import org.openrndr.draw.filterShaderFromUrl
|
||||
import org.openrndr.resourceUrl
|
||||
|
||||
class CubemapPassthrough : CubemapFilter(filterShaderFromUrl(resourceUrl("/shaders/cubemap-filters/cubemap-passthrough.frag")))
|
||||
@@ -0,0 +1,6 @@
|
||||
package org.openrndr.extra.dnk3.cubemap
|
||||
|
||||
import org.openrndr.draw.filterShaderFromUrl
|
||||
import org.openrndr.resourceUrl
|
||||
|
||||
class IrradianceConvolution : CubemapFilter(filterShaderFromUrl(resourceUrl("/shaders/cubemap-filters/irradiance-convolution.frag")))
|
||||
187
orx-dnk3/src/main/kotlin/cubemap/SphericalHarmonics.kt
Normal file
187
orx-dnk3/src/main/kotlin/cubemap/SphericalHarmonics.kt
Normal file
@@ -0,0 +1,187 @@
|
||||
@file:ShaderPhrases([])
|
||||
|
||||
package org.openrndr.extra.dnk3.cubemap
|
||||
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.extra.shaderphrases.annotations.ShaderPhrases
|
||||
import org.openrndr.extra.shaderphrases.phraseResource
|
||||
import org.openrndr.math.Vector3
|
||||
import org.openrndr.math.max
|
||||
import org.openrndr.resourceUrl
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
import kotlin.math.sqrt
|
||||
|
||||
class SphericalHarmonics : Filter(filterShaderFromUrl(resourceUrl("/shaders/cubemap-filters/spherical-harmonics.frag"))) {
|
||||
var input: Cubemap by parameters
|
||||
}
|
||||
|
||||
/** based on https://andrew-pham.blog/2019/08/26/spherical-harmonics/ */
|
||||
fun Cubemap.irradianceCoefficients(): Array<Vector3> {
|
||||
val cubemap = this
|
||||
require(cubemap.format == ColorFormat.RGB)
|
||||
require(cubemap.type == ColorType.FLOAT32)
|
||||
|
||||
val result = Array(9) { Vector3.ZERO }
|
||||
|
||||
var buffer = ByteBuffer.allocateDirect(cubemap.width * cubemap.width * cubemap.format.componentCount * cubemap.type.componentSize)
|
||||
buffer.order(ByteOrder.nativeOrder())
|
||||
|
||||
var weightSum = 0.0
|
||||
|
||||
for (side in CubemapSide.values()) {
|
||||
//cubemap.side(side).read(buffer)
|
||||
buffer.rewind()
|
||||
cubemap.read(side, buffer)
|
||||
|
||||
buffer.rewind()
|
||||
for (y in 0 until cubemap.width) {
|
||||
for (x in 0 until cubemap.width) {
|
||||
val rf = buffer.float.toDouble()
|
||||
val gf = buffer.float.toDouble()
|
||||
val bf = buffer.float.toDouble()
|
||||
|
||||
val L = Vector3(rf, gf, bf)
|
||||
|
||||
var u = (x + 0.5) / cubemap.width;
|
||||
var v = (y + 0.5) / cubemap.width;
|
||||
u = u * 2.0 - 1.0
|
||||
v = v * 2.0 - 1.0
|
||||
|
||||
val temp = 1.0 + u * u + v * v
|
||||
val weight = 4.0 / (sqrt(temp) * temp)
|
||||
|
||||
val N = cubemap.mapUVSToN(u, v, side)
|
||||
val coefficients = genLightingCoefficientsForNormal(N, L)
|
||||
|
||||
for (i in 0 until 9) {
|
||||
result[i] += coefficients[i] * weight
|
||||
}
|
||||
weightSum += weight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i in 0 until 9) {
|
||||
result[i] = result[i] * (4.0 * Math.PI) / weightSum
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fun genSHCoefficients(N: Vector3): DoubleArray {
|
||||
val result = DoubleArray(9)
|
||||
|
||||
// Band 0
|
||||
result[0] = 0.282095;
|
||||
|
||||
// Band 1
|
||||
result[1] = 0.488603 * N.y
|
||||
result[2] = 0.488603 * N.z
|
||||
result[3] = 0.488603 * N.x
|
||||
|
||||
// Band 2
|
||||
result[4] = 1.092548 * N.x * N.y
|
||||
result[5] = 1.092548 * N.y * N.z
|
||||
result[6] = 0.315392 * (3.0 * N.z * N.z - 1.0)
|
||||
result[7] = 1.092548 * N.x * N.z
|
||||
result[8] = 0.546274 * (N.x * N.x - N.y * N.y)
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
fun genLightingCoefficientsForNormal(N: Vector3, L: Vector3): Array<Vector3> {
|
||||
val coefficients = genSHCoefficients(N)
|
||||
val result = Array(9) { Vector3.ZERO }
|
||||
for (i in 0 until 9) {
|
||||
result[i] = L * coefficients[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
fun Cubemap.mapUVSToN(u: Double, v: Double, side: CubemapSide): Vector3 {
|
||||
return (side.right * u + side.up * v + side.forward).normalized
|
||||
}
|
||||
|
||||
|
||||
// Evaluates the irradiance perceived in the provided direction
|
||||
// Analytic method from http://www1.cs.columbia.edu/~ravir/papers/envmap/envmap.pdf eq. 13
|
||||
//
|
||||
fun evaluateSHIrradiance(direction: Vector3, _SH: Array<Vector3>): Vector3 {
|
||||
val c1 = 0.42904276540489171563379376569857; // 4 * Â2.Y22 = 1/4 * sqrt(15.PI)
|
||||
val c2 = 0.51166335397324424423977581244463; // 0.5 * Â1.Y10 = 1/2 * sqrt(PI/3)
|
||||
val c3 = 0.24770795610037568833406429782001; // Â2.Y20 = 1/16 * sqrt(5.PI)
|
||||
val c4 = 0.88622692545275801364908374167057; // Â0.Y00 = 1/2 * sqrt(PI)
|
||||
|
||||
val x = direction.x;
|
||||
val y = direction.y;
|
||||
val z = direction.z;
|
||||
|
||||
return max(Vector3.ZERO,
|
||||
_SH[8] * (c1 * (x * x - y * y)) // c1.L22.(x²-y²)
|
||||
+ _SH[6] * (c3 * (3.0 * z * z - 1)) // c3.L20.(3.z² - 1)
|
||||
+ _SH[0] * c4 // c4.L00
|
||||
+ (_SH[4] * x * y + _SH[7] * x * z + _SH[5] * y * z) * 2.0 * c1 // 2.c1.(L2-2.xy + L21.xz + L2-1.yz)
|
||||
+ (_SH[3] * x + _SH[1] * y + _SH[2] * z) * c2 * 2.0); // 2.c2.(L11.x + L1-1.y + L10.z)
|
||||
}
|
||||
|
||||
val glslEvaluateSH: String by phraseResource("/phrases/irradiance-sh/evaluate-sh.frag")
|
||||
|
||||
val glslFetchSH: String by phraseResource("/phrases/irradiance-sh/fetch-sh.frag")
|
||||
val glslFetchSH0: String by phraseResource("/phrases/irradiance-sh/fetch-sh0.frag")
|
||||
|
||||
fun genGlslGatherSH(xProbes: Int, yProbes: Int, zProbes: Int, spacing: Double = 1.0, offset: Vector3) = """
|
||||
ivec3 gridCoordinates(vec3 p, out vec3 f) {
|
||||
float x = (p.x - ${offset.x}) / $spacing;
|
||||
float y = (p.y - ${offset.y})/ $spacing;
|
||||
float z = (p.z - ${offset.z}) / $spacing;
|
||||
|
||||
int ix = int(floor(x)) + $xProbes / 2;
|
||||
int iy = int(floor(y)) + $yProbes / 2;
|
||||
int iz = int(floor(z)) + $zProbes / 2;
|
||||
|
||||
f.x = fract((x));
|
||||
f.y = fract((y));
|
||||
f.z = fract((z));
|
||||
|
||||
return ivec3(ix, iy, iz);
|
||||
}
|
||||
|
||||
int gridIndex(ivec3 p) {
|
||||
ivec3 c = clamp(p, ivec3(0), ivec3(${xProbes - 1}, ${yProbes - 1}, ${zProbes - 1}));
|
||||
return c.x + c.y * $xProbes + c.z * ${xProbes * yProbes};
|
||||
}
|
||||
|
||||
void gatherSH(samplerBuffer btex, vec3 p, out vec3[9] blend) {
|
||||
vec3[9] c000;
|
||||
vec3[9] c001;
|
||||
vec3[9] c010;
|
||||
vec3[9] c011;
|
||||
vec3[9] c100;
|
||||
vec3[9] c101;
|
||||
vec3[9] c110;
|
||||
vec3[9] c111;
|
||||
|
||||
vec3 f;
|
||||
ivec3 io = gridCoordinates(p, f);
|
||||
|
||||
fetchSH(btex, gridIndex(io + ivec3(0,0,0)), c000);
|
||||
fetchSH(btex, gridIndex(io + ivec3(0,0,1)), c001);
|
||||
fetchSH(btex, gridIndex(io + ivec3(0,1,0)), c010);
|
||||
fetchSH(btex, gridIndex(io + ivec3(0,1,1)), c011);
|
||||
fetchSH(btex, gridIndex(io + ivec3(1,0,0)), c100);
|
||||
fetchSH(btex, gridIndex(io + ivec3(1,0,1)), c101);
|
||||
fetchSH(btex, gridIndex(io + ivec3(1,1,0)), c110);
|
||||
fetchSH(btex, gridIndex(io + ivec3(1,1,1)), c111);
|
||||
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
blend[i] = mix( mix( mix(c000[i], c001[i], f.z), mix(c010[i], c011[i], f.z), f.y), mix( mix(c100[i], c101[i], f.z), mix(c110[i], c111[i], f.z), f.y), f.x);
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
val glslGridCoordinates: String by phraseResource("/phrases/irradiance-sh/grid-coordinates.frag")
|
||||
val glslGridIndex: String by phraseResource("/phrases/irradiance-sh/grid-index.frag")
|
||||
val glslGatherSH: String by phraseResource("/phrases/irradiance-sh/gather-sh.frag")
|
||||
val glslGatherSH0: String by phraseResource("/phrases/irradiance-sh/gather-sh0.frag")
|
||||
109
orx-dnk3/src/main/kotlin/features/IrradianceSH.kt
Normal file
109
orx-dnk3/src/main/kotlin/features/IrradianceSH.kt
Normal file
@@ -0,0 +1,109 @@
|
||||
package org.openrndr.extra.dnk3.features
|
||||
|
||||
import org.openrndr.color.ColorRGBa
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.extra.dnk3.*
|
||||
import org.openrndr.extra.dnk3.cubemap.irradianceCoefficients
|
||||
import org.openrndr.math.Matrix44
|
||||
import org.openrndr.math.Vector3
|
||||
import org.openrndr.math.transforms.transform
|
||||
import java.io.File
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
data class IrradianceSH(val xCount: Int, val yCount: Int, val zCount: Int, val spacing: Double, val offset: Vector3, val cubemapSize: Int) : Feature {
|
||||
override fun <T : Feature> update(drawer: Drawer, sceneRenderer: SceneRenderer, scene: Scene, feature: T, context: RenderContext) {
|
||||
sceneRenderer.processIrradiance(drawer, scene, feature as IrradianceSH, context)
|
||||
}
|
||||
|
||||
var shMap: BufferTexture? = null
|
||||
val probeCount
|
||||
get() = xCount * yCount * zCount
|
||||
|
||||
}
|
||||
|
||||
fun Scene.addIrradianceSH(xCount: Int,
|
||||
yCount: Int,
|
||||
zCount: Int,
|
||||
spacing: Double,
|
||||
offset: Vector3 = Vector3.ZERO,
|
||||
cubemapSize: Int = 256
|
||||
) {
|
||||
features.add(IrradianceSH(xCount * 2 + 1, yCount * 2 + 1, zCount * 2 + 1, spacing, offset, cubemapSize))
|
||||
var probeID = 0
|
||||
for (k in -zCount..zCount) {
|
||||
for (j in -yCount..yCount) {
|
||||
for (i in -xCount..xCount) {
|
||||
val probeNode = SceneNode()
|
||||
probeNode.transform = transform {
|
||||
translate(offset)
|
||||
translate(i * spacing, j * spacing, k * spacing)
|
||||
}
|
||||
probeNode.entities.add(IrradianceProbe())
|
||||
probeID++
|
||||
root.children.add(probeNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun SceneRenderer.processIrradiance(drawer: Drawer, scene: Scene, feature: IrradianceSH, context: RenderContext) {
|
||||
val irradianceProbes = scene.root.findContent { this as? IrradianceProbe }
|
||||
val irradianceProbePositions = irradianceProbes.map { it.node.worldPosition }
|
||||
|
||||
if (feature.shMap == null && irradianceProbes.isNotEmpty()) {
|
||||
val hash = scene.hash()
|
||||
val cached = File("data/scene-cache/sh-$hash.orb")
|
||||
if (cached.exists()) {
|
||||
feature.shMap = loadBufferTexture(cached)
|
||||
} else {
|
||||
var probeID = 0
|
||||
val tempCubemap = cubemap(feature.cubemapSize, format = ColorFormat.RGB, type = ColorType.FLOAT32)
|
||||
var cubemapDepthBuffer = depthBuffer(feature.cubemapSize, feature.cubemapSize, DepthFormat.DEPTH16, BufferMultisample.Disabled)
|
||||
|
||||
feature.shMap = bufferTexture(irradianceProbes.size * 9, format = ColorFormat.RGB, type = ColorType.FLOAT32)
|
||||
val buffer = ByteBuffer.allocateDirect(irradianceProbePositions.size * 9 * 3 * 4)
|
||||
buffer.order(ByteOrder.nativeOrder())
|
||||
|
||||
for ((node, probe) in irradianceProbes) {
|
||||
if (probe.dirty) {
|
||||
val pass = IrradianceProbePass
|
||||
val materialContext = MaterialContext(pass, context.lights, emptyList(), shadowLightTargets, emptyMap(), 0)
|
||||
val position = node.worldPosition
|
||||
|
||||
for (side in CubemapSide.values()) {
|
||||
val target = renderTarget(feature.cubemapSize, feature.cubemapSize) {
|
||||
//this.colorBuffer(tempCubemap.side(side))
|
||||
this.cubemap(tempCubemap, side)
|
||||
this.depthBuffer(cubemapDepthBuffer)
|
||||
}
|
||||
drawer.isolatedWithTarget(target) {
|
||||
drawer.clear(ColorRGBa.BLACK)
|
||||
drawer.projection = probe.projectionMatrix
|
||||
drawer.view = Matrix44.IDENTITY
|
||||
drawer.model = Matrix44.IDENTITY
|
||||
drawer.lookAt(position, position + side.forward, side.up)
|
||||
drawPass(drawer, pass, materialContext, context)
|
||||
}
|
||||
|
||||
target.detachDepthBuffer()
|
||||
target.detachColorAttachments()
|
||||
target.destroy()
|
||||
}
|
||||
val coefficients = tempCubemap.irradianceCoefficients()
|
||||
for (coef in coefficients) {
|
||||
buffer.putVector3((coef))
|
||||
}
|
||||
probeID++
|
||||
println("$probeID / ${irradianceProbePositions.size}")
|
||||
probe.dirty = false
|
||||
}
|
||||
}
|
||||
feature.shMap?.let {
|
||||
buffer.rewind()
|
||||
it.write(buffer)
|
||||
it.saveToFile(File("data/scene-cache/sh-$hash.orb"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,11 +24,11 @@ const val GLTF_BYTE = 5120
|
||||
const val GLTF_ARRAY_BUFFER = 34962
|
||||
const val GLTF_ELEMENT_ARRAY_BUFFER = 34963
|
||||
|
||||
class GltfAsset(val generator: String?, val version: String?)
|
||||
data class GltfAsset(val generator: String?, val version: String?)
|
||||
|
||||
class GltfScene(val nodes: IntArray)
|
||||
data class GltfScene(val nodes: IntArray)
|
||||
|
||||
class GltfNode(val name: String,
|
||||
data class GltfNode(val name: String,
|
||||
val children: IntArray?,
|
||||
val matrix: DoubleArray?,
|
||||
val scale: DoubleArray?,
|
||||
@@ -39,14 +39,14 @@ class GltfNode(val name: String,
|
||||
val camera: Int?,
|
||||
val extensions: GltfNodeExtensions?)
|
||||
|
||||
class KHRLightsPunctualIndex(val light: Int)
|
||||
data class KHRLightsPunctualIndex(val light: Int)
|
||||
|
||||
class GltfNodeExtensions(val KHR_lights_punctual: KHRLightsPunctualIndex?) {
|
||||
data class GltfNodeExtensions(val KHR_lights_punctual: KHRLightsPunctualIndex?) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
class GltfPrimitive(val attributes: LinkedHashMap<String, Int>, val indices: Int?, val mode: Int?, val material: Int) {
|
||||
data class GltfPrimitive(val attributes: LinkedHashMap<String, Int>, val indices: Int?, val mode: Int?, val material: Int) {
|
||||
fun createDrawCommand(gltfFile: GltfFile): GltfDrawCommand {
|
||||
|
||||
val indexBuffer = indices?.let { indices ->
|
||||
@@ -164,27 +164,27 @@ class GltfPrimitive(val attributes: LinkedHashMap<String, Int>, val indices: Int
|
||||
}
|
||||
}
|
||||
|
||||
class GltfMesh(val primitives: List<GltfPrimitive>, val name: String) {
|
||||
data class GltfMesh(val primitives: List<GltfPrimitive>, val name: String) {
|
||||
fun createDrawCommands(gltfFile: GltfFile): List<GltfDrawCommand> {
|
||||
return primitives.map { it.createDrawCommand(gltfFile) }
|
||||
}
|
||||
}
|
||||
|
||||
class GltfPbrMetallicRoughness(val baseColorFactor: DoubleArray?,
|
||||
data class GltfPbrMetallicRoughness(val baseColorFactor: DoubleArray?,
|
||||
val baseColorTexture: GltfMaterialTexture?,
|
||||
var metallicRoughnessTexture: GltfMaterialTexture?,
|
||||
val roughnessFactor: Double?,
|
||||
val metallicFactor: Double?)
|
||||
|
||||
class GltfMaterialTexture(val index: Int, val scale: Double?, val texCoord: Int?)
|
||||
data class GltfMaterialTexture(val index: Int, val scale: Double?, val texCoord: Int?)
|
||||
|
||||
class GltfImage(val uri: String?, val bufferView: Int?)
|
||||
data class GltfImage(val uri: String?, val bufferView: Int?)
|
||||
|
||||
class GltfSampler(val magFilter: Int, val minFilter: Int, val wrapS: Int, val wrapT: Int)
|
||||
data class GltfSampler(val magFilter: Int, val minFilter: Int, val wrapS: Int, val wrapT: Int)
|
||||
|
||||
class GltfTexture(val sampler: Int, val source: Int)
|
||||
data class GltfTexture(val sampler: Int, val source: Int)
|
||||
|
||||
class GltfMaterial(val name: String,
|
||||
data class GltfMaterial(val name: String,
|
||||
val alphaMode: String?,
|
||||
val doubleSided: Boolean?,
|
||||
val normalTexture: GltfMaterialTexture?,
|
||||
@@ -195,19 +195,19 @@ class GltfMaterial(val name: String,
|
||||
val extensions: GltfMaterialExtensions?
|
||||
)
|
||||
|
||||
class GltfMaterialExtensions(
|
||||
data class GltfMaterialExtensions(
|
||||
val KHR_materials_pbrSpecularGlossiness: KhrMaterialsPbrSpecularGlossiness?
|
||||
)
|
||||
|
||||
class KhrMaterialsPbrSpecularGlossiness(val diffuseFactor: DoubleArray?, val diffuseTexture: GltfMaterialTexture?)
|
||||
|
||||
class GltfBufferView(val buffer: Int,
|
||||
data class GltfBufferView(val buffer: Int,
|
||||
val byteOffset: Int?,
|
||||
val byteLength: Int,
|
||||
val byteStride: Int?,
|
||||
val target: Int)
|
||||
|
||||
class GltfBuffer(val byteLength: Int, val uri: String?) {
|
||||
data class GltfBuffer(val byteLength: Int, val uri: String?) {
|
||||
fun contents(gltfFile: GltfFile): ByteBuffer = if (uri != null) {
|
||||
if (uri.startsWith("data:")) {
|
||||
val base64 = uri.substring(uri.indexOf(",") + 1)
|
||||
@@ -231,9 +231,9 @@ class GltfBuffer(val byteLength: Int, val uri: String?) {
|
||||
}
|
||||
}
|
||||
|
||||
class GltfDrawCommand(val vertexBuffer: VertexBuffer, val indexBuffer: IndexBuffer?, val primitive: DrawPrimitive, var vertexCount: Int)
|
||||
data class GltfDrawCommand(val vertexBuffer: VertexBuffer, val indexBuffer: IndexBuffer?, val primitive: DrawPrimitive, var vertexCount: Int)
|
||||
|
||||
class GltfAccessor(
|
||||
data class GltfAccessor(
|
||||
val bufferView: Int,
|
||||
val byteOffset: Int,
|
||||
val componentType: Int,
|
||||
@@ -243,28 +243,28 @@ class GltfAccessor(
|
||||
val type: String
|
||||
)
|
||||
|
||||
class GltfAnimation(val name: String?, val channels: List<GltfChannel>, val samplers: List<GltfAnimationSampler>)
|
||||
class GltfAnimationSampler(val input: Int, val interpolation: String, val output: Int)
|
||||
data class GltfAnimation(val name: String?, val channels: List<GltfChannel>, val samplers: List<GltfAnimationSampler>)
|
||||
data class GltfAnimationSampler(val input: Int, val interpolation: String, val output: Int)
|
||||
|
||||
class GltfChannelTarget(val node: Int?, val path: String?)
|
||||
data class GltfChannelTarget(val node: Int?, val path: String?)
|
||||
|
||||
class GltfChannel(val sampler: Int, val target: GltfChannelTarget)
|
||||
data class GltfChannel(val sampler: Int, val target: GltfChannelTarget)
|
||||
|
||||
|
||||
class GltfSkin(val inverseBindMatrices: Int, val joints: IntArray, val skeleton: Int)
|
||||
data class GltfSkin(val inverseBindMatrices: Int, val joints: IntArray, val skeleton: Int)
|
||||
|
||||
|
||||
class KHRLightsPunctualLight(val color: DoubleArray?, val type: String, val intensity: Double?, val range: Double, val spot: KHRLightsPunctualLightSpot?)
|
||||
class KHRLightsPunctualLightSpot(val innerConeAngle: Double?, val outerConeAngle: Double?)
|
||||
data class KHRLightsPunctualLight(val color: DoubleArray?, val type: String, val intensity: Double?, val range: Double, val spot: KHRLightsPunctualLightSpot?)
|
||||
data class KHRLightsPunctualLightSpot(val innerConeAngle: Double?, val outerConeAngle: Double?)
|
||||
|
||||
class KHRLightsPunctual(val lights: List<KHRLightsPunctualLight>)
|
||||
data class KHRLightsPunctual(val lights: List<KHRLightsPunctualLight>)
|
||||
|
||||
class GltfExtensions(val KHR_lights_punctual: KHRLightsPunctual?)
|
||||
data class GltfExtensions(val KHR_lights_punctual: KHRLightsPunctual?)
|
||||
|
||||
class GltfCameraPerspective(val aspectRatio: Double?, val yfov: Double, val zfar: Double?, val znear: Double)
|
||||
class GltfCameraOrthographic(val xmag: Double, val ymag: Double, val zfar: Double, val znear: Double)
|
||||
data class GltfCameraPerspective(val aspectRatio: Double?, val yfov: Double, val zfar: Double?, val znear: Double)
|
||||
data class GltfCameraOrthographic(val xmag: Double, val ymag: Double, val zfar: Double, val znear: Double)
|
||||
|
||||
class GltfCamera(val name: String?, val type: String, val perspective: GltfCameraPerspective?, val orthographic: GltfCameraOrthographic?)
|
||||
data class GltfCamera(val name: String?, val type: String, val perspective: GltfCameraPerspective?, val orthographic: GltfCameraOrthographic?)
|
||||
|
||||
class GltfFile(
|
||||
val asset: GltfAsset?,
|
||||
|
||||
@@ -15,13 +15,10 @@ import java.nio.ByteOrder
|
||||
import kotlin.reflect.KMutableProperty0
|
||||
|
||||
class SceneAnimation(var channels: List<AnimationChannel>) {
|
||||
|
||||
val duration: Double
|
||||
get() {
|
||||
return channels.maxBy { it.duration }?.duration ?: 0.0
|
||||
}
|
||||
|
||||
|
||||
fun applyToTargets(input: Double) {
|
||||
for (channel in channels) {
|
||||
channel.applyToTarget(input)
|
||||
@@ -34,6 +31,7 @@ sealed class AnimationChannel {
|
||||
abstract fun applyToTarget(input: Double)
|
||||
}
|
||||
|
||||
|
||||
class QuaternionChannel(val target: KMutableProperty0<Quaternion>,
|
||||
val keyframer: KeyframerChannelQuaternion) : AnimationChannel() {
|
||||
override fun applyToTarget(input: Double) {
|
||||
@@ -63,7 +61,6 @@ class GltfSceneNode : SceneNode() {
|
||||
return "translation: $translation, scale: $scale, rotation: $rotation, children: ${children.size}, entities: ${entities} "
|
||||
}
|
||||
|
||||
|
||||
override var transform: Matrix44 = Matrix44.IDENTITY
|
||||
get() = transform {
|
||||
translate(translation)
|
||||
@@ -90,7 +87,12 @@ fun GltfFile.buildSceneNodes(): GltfSceneData {
|
||||
require(localBufferView.byteLength != null)
|
||||
localBuffer.position(localBufferView.byteOffset)
|
||||
localBuffer.limit(localBufferView.byteOffset + localBufferView.byteLength)
|
||||
ColorBuffer.fromBuffer(localBuffer)
|
||||
val cb = ColorBuffer.fromBuffer(localBuffer)
|
||||
cb.generateMipmaps()
|
||||
cb.filter(MinifyingFilter.LINEAR_MIPMAP_LINEAR, MagnifyingFilter.LINEAR)
|
||||
cb.anisotropy = 100.0
|
||||
localBuffer.limit(localBuffer.capacity())
|
||||
cb
|
||||
} ?: error("no uri and no bufferview")
|
||||
|
||||
} else {
|
||||
@@ -106,6 +108,7 @@ fun GltfFile.buildSceneNodes(): GltfSceneData {
|
||||
val sceneMaterials = mutableMapOf<GltfMaterial, Material>()
|
||||
fun GltfMaterial.createSceneMaterial(): Material = sceneMaterials.getOrPut(this) {
|
||||
val material = PBRMaterial()
|
||||
material.name = this.name
|
||||
|
||||
material.doubleSided = this.doubleSided ?: false
|
||||
material.transparent = this.alphaMode != null
|
||||
@@ -203,7 +206,7 @@ fun GltfFile.buildSceneNodes(): GltfSceneData {
|
||||
drawCommand.primitive,
|
||||
0,
|
||||
drawCommand.vertexCount)
|
||||
val material = materials[material].createSceneMaterial()
|
||||
val material = materials?.getOrNull(material)?.createSceneMaterial() ?: PBRMaterial()
|
||||
return MeshPrimitive(geometry, material)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.openrndr.extra.dnk3.materials
|
||||
|
||||
import org.openrndr.draw.ShadeStyle
|
||||
import org.openrndr.draw.shadeStyle
|
||||
import org.openrndr.extra.dnk3.Material
|
||||
import org.openrndr.extra.dnk3.MaterialContext
|
||||
import org.openrndr.extra.dnk3.PrimitiveContext
|
||||
import org.openrndr.extra.dnk3.cubemap.glslEvaluateSH
|
||||
import org.openrndr.extra.dnk3.cubemap.glslFetchSH
|
||||
import org.openrndr.extra.dnk3.cubemap.genGlslGatherSH
|
||||
|
||||
class IrradianceDebugMaterial : Material {
|
||||
override val name: String? = null
|
||||
|
||||
override var doubleSided: Boolean = false
|
||||
override var transparent: Boolean = false
|
||||
override val fragmentID: Int = 0
|
||||
|
||||
override fun generateShadeStyle(context: MaterialContext, primitiveContext: PrimitiveContext): ShadeStyle {
|
||||
return shadeStyle {
|
||||
fragmentPreamble = """
|
||||
$glslEvaluateSH
|
||||
$glslFetchSH
|
||||
${genGlslGatherSH(context.irradianceSH!!.xCount, context.irradianceSH!!.yCount, context.irradianceSH!!.zCount, context.irradianceSH!!.spacing, context.irradianceSH!!.offset)}
|
||||
vec3 f_emission = vec3(0.0);
|
||||
"""
|
||||
|
||||
if (context.irradianceSH != null) {
|
||||
fragmentTransform = """
|
||||
vec3[9] sh;
|
||||
gatherSH(p_shMap, v_worldPosition, sh);
|
||||
x_fill.rgb = evaluateSH(normalize(v_worldNormal), sh);
|
||||
|
||||
""".trimIndent()
|
||||
} else {
|
||||
fragmentTransform = """
|
||||
discard;
|
||||
"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun applyToShadeStyle(context: MaterialContext, shadeStyle: ShadeStyle) {
|
||||
context.irradianceSH?.shMap?.let {
|
||||
shadeStyle.parameter("shMap", it)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is IrradianceDebugMaterial) return false
|
||||
|
||||
if (name != other.name) return false
|
||||
if (doubleSided != other.doubleSided) return false
|
||||
if (transparent != other.transparent) return false
|
||||
if (fragmentID != other.fragmentID) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = name?.hashCode() ?: 0
|
||||
result = 31 * result + doubleSided.hashCode()
|
||||
result = 31 * result + transparent.hashCode()
|
||||
result = 31 * result + fragmentID
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
38
orx-dnk3/src/main/kotlin/post/ScreenspaceReflections.kt
Normal file
38
orx-dnk3/src/main/kotlin/post/ScreenspaceReflections.kt
Normal file
@@ -0,0 +1,38 @@
|
||||
package org.openrndr.extra.dnk3.post
|
||||
|
||||
import org.openrndr.draw.Filter
|
||||
import org.openrndr.draw.Shader
|
||||
import org.openrndr.draw.filterShaderFromUrl
|
||||
import org.openrndr.math.Matrix44
|
||||
import org.openrndr.resourceUrl
|
||||
|
||||
class ScreenspaceReflections : Filter(preprocessedFilterShaderFromUrl(resourceUrl("/shaders/screenspace-reflections.frag"))) {
|
||||
var projection: Matrix44 by parameters
|
||||
var projectionMatrixInverse: Matrix44 by parameters
|
||||
|
||||
var colors: Int by parameters
|
||||
var projDepth: Int by parameters
|
||||
var normals: Int by parameters
|
||||
|
||||
var jitterOriginGain: Double by parameters
|
||||
var iterationLimit: Int by parameters
|
||||
var distanceLimit: Double by parameters
|
||||
var gain: Double by parameters
|
||||
var borderWidth: Double by parameters
|
||||
|
||||
init {
|
||||
colors = 0
|
||||
projDepth = 1
|
||||
normals = 2
|
||||
|
||||
projection = Matrix44.IDENTITY
|
||||
projectionMatrixInverse = Matrix44.IDENTITY
|
||||
|
||||
distanceLimit = 100.0
|
||||
iterationLimit = 128
|
||||
jitterOriginGain = 0.0
|
||||
|
||||
gain = 1.0
|
||||
borderWidth = 130.0
|
||||
}
|
||||
}
|
||||
45
orx-dnk3/src/main/kotlin/post/VolumetricIrradiance.kt
Normal file
45
orx-dnk3/src/main/kotlin/post/VolumetricIrradiance.kt
Normal file
@@ -0,0 +1,45 @@
|
||||
package org.openrndr.extra.dnk3.post
|
||||
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.extra.dnk3.features.IrradianceSH
|
||||
import org.openrndr.extra.shaderphrases.preprocessShader
|
||||
import org.openrndr.math.IntVector3
|
||||
import org.openrndr.math.Matrix44
|
||||
import org.openrndr.resourceUrl
|
||||
import java.net.URL
|
||||
|
||||
fun preprocessedFilterShaderFromUrl(url: String): Shader {
|
||||
return filterShaderFromCode( preprocessShader(URL(url).readText()), "filter-shader: $url")
|
||||
}
|
||||
|
||||
fun preprocessedFilterShaderFromCode(fragmentShaderCode: String, name: String): Shader {
|
||||
return Shader.createFromCode(Filter.filterVertexCode, fragmentShaderCode, name)
|
||||
}
|
||||
|
||||
class VolumetricIrradiance : Filter(preprocessedFilterShaderFromUrl(resourceUrl("/shaders/volumetric-irradiance.frag"))) {
|
||||
|
||||
var stepLength: Double by parameters
|
||||
var irradianceSH: IrradianceSH? = null
|
||||
|
||||
var viewMatrixInverse: Matrix44 by parameters
|
||||
var projectionMatrixInverse: Matrix44 by parameters
|
||||
|
||||
init {
|
||||
stepLength = 0.1
|
||||
viewMatrixInverse = Matrix44.IDENTITY
|
||||
projectionMatrixInverse = Matrix44.IDENTITY
|
||||
}
|
||||
|
||||
override fun apply(source: Array<ColorBuffer>, target: Array<ColorBuffer>) {
|
||||
irradianceSH?.shMap?.let {
|
||||
parameters["shMap"] = it
|
||||
}
|
||||
irradianceSH?.let {
|
||||
parameters["shMapDimensions"] = IntVector3(it.xCount, it.yCount, it.zCount)
|
||||
parameters["shMapOffset"] = it.offset
|
||||
parameters["shMapSpacing"] = it.spacing
|
||||
}
|
||||
super.apply(source, target)
|
||||
}
|
||||
}
|
||||
|
||||
56
orx-dnk3/src/main/kotlin/query/Query.kt
Normal file
56
orx-dnk3/src/main/kotlin/query/Query.kt
Normal file
@@ -0,0 +1,56 @@
|
||||
package org.openrndr.extra.dnk3.query
|
||||
|
||||
import org.openrndr.extra.dnk3.Material
|
||||
import org.openrndr.extra.dnk3.Mesh
|
||||
import org.openrndr.extra.dnk3.Scene
|
||||
import org.openrndr.extra.dnk3.SceneNode
|
||||
|
||||
fun Scene.findNodeByName(name: String): SceneNode? {
|
||||
return root.findNodeByName(name)
|
||||
}
|
||||
|
||||
fun SceneNode.findNodeByName(name: String): SceneNode? {
|
||||
|
||||
if (this.name == name) {
|
||||
return this
|
||||
} else {
|
||||
for (child in children) {
|
||||
val candidate = child.findNodeByName(name)
|
||||
if (candidate != null) {
|
||||
return candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun SceneNode.findMaterialByName(name: String): Material? {
|
||||
return allMaterials().find { it.name == name }
|
||||
}
|
||||
|
||||
fun Scene.allMaterials(): Set<Material> {
|
||||
return root.allMaterials()
|
||||
}
|
||||
|
||||
fun SceneNode.allMaterials(): Set<Material> {
|
||||
val materials = mutableSetOf<Material>()
|
||||
fun processNode(node: SceneNode) {
|
||||
for (entity in node.entities) {
|
||||
when (entity) {
|
||||
is Mesh -> {
|
||||
materials.addAll(entity.primitives.map { it.material })
|
||||
}
|
||||
else -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (child in node.children) {
|
||||
processNode(child)
|
||||
}
|
||||
}
|
||||
processNode(this)
|
||||
return materials
|
||||
}
|
||||
|
||||
|
||||
8
orx-dnk3/src/main/kotlin/renderers/DryRenderer.kt
Normal file
8
orx-dnk3/src/main/kotlin/renderers/DryRenderer.kt
Normal file
@@ -0,0 +1,8 @@
|
||||
package org.openrndr.extra.dnk3.renderers
|
||||
|
||||
import org.openrndr.extra.dnk3.SceneRenderer
|
||||
|
||||
fun dryRenderer() : SceneRenderer {
|
||||
val sr = SceneRenderer()
|
||||
return sr
|
||||
}
|
||||
22
orx-dnk3/src/main/kotlin/renderers/PostRenderer.kt
Normal file
22
orx-dnk3/src/main/kotlin/renderers/PostRenderer.kt
Normal file
@@ -0,0 +1,22 @@
|
||||
package org.openrndr.extra.dnk3.renderers
|
||||
|
||||
import org.openrndr.draw.BufferMultisample
|
||||
import org.openrndr.draw.ColorFormat
|
||||
import org.openrndr.draw.ColorType
|
||||
import org.openrndr.extra.dnk3.*
|
||||
import org.openrndr.extra.dnk3.post.SegmentContours
|
||||
import org.openrndr.extra.dnk3.post.SegmentContoursMSAA8
|
||||
|
||||
fun postRenderer(multisample: BufferMultisample = BufferMultisample.Disabled): SceneRenderer {
|
||||
val sr = SceneRenderer()
|
||||
sr.outputPasses.clear()
|
||||
sr.outputPasses.add(
|
||||
RenderPass(
|
||||
listOf(HDRColorFacet(),FragmentIDFacet(), ClipDepthFacet(), ViewNormalFacet()),
|
||||
multisample = multisample
|
||||
)
|
||||
)
|
||||
|
||||
sr.drawFinalBuffer = true
|
||||
return sr
|
||||
}
|
||||
@@ -12,7 +12,7 @@ fun segmentContourRenderer(multisample: BufferMultisample = BufferMultisample.Di
|
||||
sr.outputPasses.clear()
|
||||
sr.outputPasses.add(
|
||||
RenderPass(
|
||||
listOf(FragmentIDFacet()),
|
||||
listOf(LDRColorFacet(),FragmentIDFacet()),
|
||||
multisample = multisample
|
||||
)
|
||||
)
|
||||
|
||||
98
orx-dnk3/src/main/kotlin/tools/MeshCollapse.kt
Normal file
98
orx-dnk3/src/main/kotlin/tools/MeshCollapse.kt
Normal file
@@ -0,0 +1,98 @@
|
||||
package org.openrndr.extra.dnk3.tools
|
||||
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.extra.dnk3.Geometry
|
||||
import org.openrndr.extra.dnk3.Mesh
|
||||
import org.openrndr.extra.dnk3.MeshPrimitive
|
||||
import org.openrndr.extra.dnk3.PBRMaterial
|
||||
import java.nio.ByteBuffer
|
||||
import java.nio.ByteOrder
|
||||
|
||||
|
||||
private data class CollapseItem(val vertexFormats: List<VertexFormat>,
|
||||
val drawPrimitive: DrawPrimitive,
|
||||
val hasIndexBuffer: Boolean)
|
||||
|
||||
fun Mesh.collapse() {
|
||||
val grouped = primitives.groupBy {
|
||||
CollapseItem(it.geometry.vertexBuffers.map { it.vertexFormat }, it.geometry.primitive, it.geometry.indexBuffer != null)
|
||||
}
|
||||
|
||||
grouped.map {
|
||||
val vertexCount = it.value.sumBy { primitive ->
|
||||
primitive.geometry.vertexCount
|
||||
}
|
||||
|
||||
val indexCount = if (it.key.hasIndexBuffer)
|
||||
it.value.sumBy { primitive ->
|
||||
primitive.geometry.indexBuffer?.indexCount ?: 0
|
||||
}
|
||||
else 0
|
||||
|
||||
val collapsedVertices = it.key.vertexFormats.map {
|
||||
vertexBuffer(it, vertexCount)
|
||||
} + vertexBuffer(vertexFormat { attribute("fragmentID", VertexElementType.INT16) }, vertexCount)
|
||||
|
||||
|
||||
val fragmentBuffer = ByteBuffer.allocateDirect(vertexCount * 2)
|
||||
fragmentBuffer.order(ByteOrder.nativeOrder())
|
||||
|
||||
for (i in 0 until collapsedVertices.size) {
|
||||
var offset = 0
|
||||
for (fromPrimitive in it.value) {
|
||||
val fromBuffer = fromPrimitive.geometry.vertexBuffers[i]
|
||||
|
||||
val copy = ByteBuffer.allocateDirect(fromBuffer.vertexCount * fromBuffer.vertexFormat.size)
|
||||
copy.order(ByteOrder.nativeOrder())
|
||||
fromBuffer.read(copy)
|
||||
copy.rewind()
|
||||
|
||||
collapsedVertices[i].write(copy, offset)
|
||||
offset += copy.capacity()
|
||||
|
||||
for (v in 0 until fromBuffer.vertexCount) {
|
||||
fragmentBuffer.putShort(fromPrimitive.material.fragmentID.toShort())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val collapsedIndices = if (it.key.hasIndexBuffer) indexBuffer(indexCount, IndexType.INT32) else null
|
||||
|
||||
if (it.key.hasIndexBuffer) {
|
||||
var offset = 0
|
||||
val result = ByteBuffer.allocateDirect(4 * indexCount)
|
||||
result.order(ByteOrder.nativeOrder())
|
||||
|
||||
for (fromPrimitive in it.value) {
|
||||
val fromBuffer = fromPrimitive.geometry.indexBuffer!!
|
||||
when (fromBuffer.type) {
|
||||
IndexType.INT16 -> {
|
||||
val copy = ByteBuffer.allocateDirect(fromBuffer.indexCount * 2)
|
||||
fromBuffer.read(copy)
|
||||
copy.rewind()
|
||||
for (i in 0 until fromBuffer.indexCount) {
|
||||
val index = (copy.getShort().toInt() and 0xffff) + offset
|
||||
result.putInt(index)
|
||||
}
|
||||
}
|
||||
IndexType.INT32 -> {
|
||||
val copy = ByteBuffer.allocateDirect(fromBuffer.indexCount * 4)
|
||||
fromBuffer.read(copy)
|
||||
copy.rewind()
|
||||
for (i in 0 until fromBuffer.indexCount) {
|
||||
val index = copy.getInt() + offset
|
||||
result.putInt(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += fromPrimitive.geometry.vertexCount
|
||||
}
|
||||
}
|
||||
|
||||
val collapsedGeometry = Geometry(collapsedVertices, collapsedIndices, it.key.drawPrimitive, 0, if (collapsedIndices == null)
|
||||
vertexCount else indexCount
|
||||
)
|
||||
|
||||
MeshPrimitive(collapsedGeometry, PBRMaterial())
|
||||
}
|
||||
}
|
||||
84
orx-dnk3/src/main/kotlin/tools/Skybox.kt
Normal file
84
orx-dnk3/src/main/kotlin/tools/Skybox.kt
Normal file
@@ -0,0 +1,84 @@
|
||||
package org.openrndr.extra.dnk3.tools
|
||||
|
||||
import org.openrndr.draw.*
|
||||
import org.openrndr.extra.dnk3.*
|
||||
import org.openrndr.extras.meshgenerators.boxMesh
|
||||
|
||||
|
||||
data class SkyboxMaterial(val cubemap: Cubemap, val intensity: Double = 0.0) : Material {
|
||||
override val name: String = "skybox"
|
||||
override var doubleSided: Boolean = false
|
||||
override var transparent: Boolean = false
|
||||
override val fragmentID: Int = 0
|
||||
|
||||
override fun generateShadeStyle(materialContext: MaterialContext, primitiveContext: PrimitiveContext): ShadeStyle {
|
||||
return shadeStyle {
|
||||
vertexTransform = """
|
||||
vec2 i = vec2(1.0, 0.0);
|
||||
x_viewMatrix = x_viewNormalMatrix;
|
||||
""".trimIndent()
|
||||
|
||||
val combinerFS = materialContext.pass.combiners.map {
|
||||
it.generateShader()
|
||||
}.joinToString("\n")
|
||||
|
||||
fragmentPreamble = """
|
||||
vec4 f_diffuse = vec4(0.0, 0.0, 0.0, 1.0);
|
||||
vec3 f_specular = vec3(0.0);
|
||||
vec3 f_ambient = vec3(0.0);
|
||||
vec3 f_emission = vec3(0.0);
|
||||
int f_fragmentID = 0;
|
||||
vec4 m_color = vec4(1.0);
|
||||
vec4 f_fog = vec4(0.0);
|
||||
|
||||
""".trimIndent()
|
||||
fragmentTransform = """
|
||||
f_diffuse = texture(p_skybox, va_position);
|
||||
f_diffuse.rgb *= p_intensity;
|
||||
""" + combinerFS
|
||||
|
||||
suppressDefaultOutput = true
|
||||
val rt = RenderTarget.active
|
||||
materialContext.pass.combiners.map {
|
||||
if (rt is ProgramRenderTarget || materialContext.pass === DefaultPass || materialContext.pass === DefaultOpaquePass || materialContext.pass == DefaultTransparentPass || materialContext.pass == IrradianceProbePass) {
|
||||
this.output(it.targetOutput, ShadeStyleOutput(0))
|
||||
} else {
|
||||
val index = rt.colorAttachmentIndexByName(it.targetOutput)
|
||||
?: error("attachment ${it.targetOutput} not found")
|
||||
val type = rt.colorBuffer(index).type
|
||||
val format = rt.colorBuffer(index).format
|
||||
this.output(it.targetOutput, ShadeStyleOutput(index, format, type))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun applyToShadeStyle(context: MaterialContext, shadeStyle: ShadeStyle) {
|
||||
shadeStyle.parameter("skybox", cubemap)
|
||||
shadeStyle.parameter("intensity", intensity)
|
||||
}
|
||||
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = intensity.hashCode()
|
||||
result = 31 * result + name.hashCode()
|
||||
result = 31 * result + doubleSided.hashCode()
|
||||
result = 31 * result + transparent.hashCode()
|
||||
result = 31 * result + fragmentID
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fun Scene.addSkybox(cubemapUrl: String, size: Double = 100.0, intensity:Double = 1.0) {
|
||||
val cubemap = Cubemap.fromUrl(cubemapUrl, Session.active)
|
||||
val box = boxMesh(size, size, size, 1, 1, 1, true)
|
||||
val node = SceneNode()
|
||||
val material = SkyboxMaterial(cubemap, intensity)
|
||||
val geometry = Geometry(listOf(box), null, DrawPrimitive.TRIANGLES, 0, box.vertexCount)
|
||||
val primitive = MeshPrimitive(geometry, material)
|
||||
val mesh = Mesh(listOf(primitive))
|
||||
node.entities.add(mesh)
|
||||
root.children.add(node)
|
||||
}
|
||||
Reference in New Issue
Block a user