[orx-dnk3] Add feature architecture and post processing effects

This commit is contained in:
Edwin Jakobs
2020-07-06 13:00:58 +02:00
parent 0b0691e9ae
commit 419c38cc25
63 changed files with 2164 additions and 187 deletions

View File

@@ -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
}
}

View File

@@ -1,6 +0,0 @@
package org.openrndr.extra.dnk3
fun dryRenderer() : SceneRenderer {
val sr = SceneRenderer()
return sr
}

View File

@@ -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()
}
}

View File

@@ -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);
"""
}

View 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
)
}

View File

@@ -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()
}
}

View File

@@ -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)

View File

@@ -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
}
}

View File

@@ -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(),

View File

@@ -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())

View File

@@ -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

View File

@@ -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())
}

View File

@@ -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);

View 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
}

View 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")))

View File

@@ -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")))

View 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")

View 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"))
}
}
}
}

View File

@@ -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?,

View File

@@ -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)
}

View File

@@ -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
}
}

View 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
}
}

View 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)
}
}

View 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
}

View 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
}

View 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
}

View File

@@ -12,7 +12,7 @@ fun segmentContourRenderer(multisample: BufferMultisample = BufferMultisample.Di
sr.outputPasses.clear()
sr.outputPasses.add(
RenderPass(
listOf(FragmentIDFacet()),
listOf(LDRColorFacet(),FragmentIDFacet()),
multisample = multisample
)
)

View 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())
}
}

View 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)
}