From c1d0963da4a1e10d0edd710f352d09de063b4a83 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Fri, 9 Feb 2024 12:08:16 +0100 Subject: [PATCH] Rewrite the GLSL ray trace code so it does not use global variables!! Fix portals not working --- src/common/rendering/vulkan/vk_levelmesh.cpp | 2 +- src/rendering/hwrenderer/doom_levelmesh.cpp | 23 ++- src/rendering/hwrenderer/doom_levelmesh.h | 2 +- .../shaders/lightmap/polyfill_rayquery.glsl | 44 ++++-- .../lightmap/trace_ambient_occlusion.glsl | 17 ++- .../shaders/lightmap/trace_levelmesh.glsl | 142 +++--------------- .../static/shaders/lightmap/trace_light.glsl | 47 ++++-- .../shaders/lightmap/trace_sunlight.glsl | 53 ++++--- 8 files changed, 155 insertions(+), 175 deletions(-) diff --git a/src/common/rendering/vulkan/vk_levelmesh.cpp b/src/common/rendering/vulkan/vk_levelmesh.cpp index 9c41e4219..246a1585d 100644 --- a/src/common/rendering/vulkan/vk_levelmesh.cpp +++ b/src/common/rendering/vulkan/vk_levelmesh.cpp @@ -630,7 +630,7 @@ void VkLevelMeshUploader::UploadPortals() size_t copysize = range.Size * sizeof(PortalInfo); if (copysize > 0) - cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->PortalBuffer.get(), range.Offset * sizeof(PortalInfo), 0, copysize); + cmdbuffer->copyBuffer(transferBuffer.get(), Mesh->PortalBuffer.get(), datapos, range.Offset * sizeof(PortalInfo), copysize); datapos += copysize; } } diff --git a/src/rendering/hwrenderer/doom_levelmesh.cpp b/src/rendering/hwrenderer/doom_levelmesh.cpp index 67cc07871..c110405b4 100644 --- a/src/rendering/hwrenderer/doom_levelmesh.cpp +++ b/src/rendering/hwrenderer/doom_levelmesh.cpp @@ -198,7 +198,9 @@ bool DoomLevelMesh::TraceSky(const FVector3& start, FVector3 direction, float di int DoomLevelMesh::AddSurfaceLights(const LevelMeshSurface* surface, LevelMeshLight* list, int listMaxSize) { - FLightNode* node = GetSurfaceLightNode(static_cast(surface)); + std::pair nodePortalGroup = GetSurfaceLightNode(static_cast(surface)); + FLightNode* node = nodePortalGroup.first; + int portalgroup = nodePortalGroup.second; if (!node) return 0; @@ -208,7 +210,7 @@ int DoomLevelMesh::AddSurfaceLights(const LevelMeshSurface* surface, LevelMeshLi FDynamicLight* light = node->lightsource; if (light && light->Trace()) { - DVector3 pos = light->Pos; //light->PosRelative(portalgroup); + DVector3 pos = light->PosRelative(portalgroup); LevelMeshLight& meshlight = list[listpos++]; meshlight.Origin = { (float)pos.X, (float)pos.Y, (float)pos.Z }; @@ -251,12 +253,14 @@ int DoomLevelMesh::AddSurfaceLights(const LevelMeshSurface* surface, LevelMeshLi return listpos; } -FLightNode* DoomLevelMesh::GetSurfaceLightNode(const DoomLevelMeshSurface* doomsurf) +std::pair DoomLevelMesh::GetSurfaceLightNode(const DoomLevelMeshSurface* doomsurf) { FLightNode* node = nullptr; + int portalgroup = 0; if (doomsurf->Type == ST_FLOOR || doomsurf->Type == ST_CEILING) { node = doomsurf->Subsector->section->lighthead; + portalgroup = doomsurf->Subsector->sector->PortalGroup; } else if (doomsurf->Type == ST_MIDDLESIDE || doomsurf->Type == ST_UPPERSIDE || doomsurf->Type == ST_LOWERSIDE) { @@ -265,18 +269,21 @@ FLightNode* DoomLevelMesh::GetSurfaceLightNode(const DoomLevelMeshSurface* dooms { subsector_t* subsector = level.PointInRenderSubsector((doomsurf->Side->V1()->fPos() + doomsurf->Side->V2()->fPos()) * 0.5); node = subsector->section->lighthead; + portalgroup = subsector->sector->PortalGroup; } else if (!doomsurf->ControlSector) { node = doomsurf->Side->lighthead; + portalgroup = doomsurf->Side->sector->PortalGroup; } else // 3d floor needs light from the sidedef on the other side { int otherside = doomsurf->Side->linedef->sidedef[0] == doomsurf->Side ? 1 : 0; node = doomsurf->Side->linedef->sidedef[otherside]->lighthead; + portalgroup = doomsurf->Side->linedef->sidedef[otherside]->sector->PortalGroup; } } - return node; + return { node, portalgroup }; } void DoomLevelMesh::CreateSurfaces(FLevelLocals& doomMap) @@ -1021,9 +1028,10 @@ void DoomLevelMesh::CreatePortals(FLevelLocals& doomMap) auto d = sector->GetPortalDisplacement(plane); if (!d.isZero()) { + // Note: Y and Z is swapped in the shader due to how the hwrenderer was implemented VSMatrix transformation; transformation.loadIdentity(); - transformation.translate((float)d.X, (float)d.Y, 0.0f); + transformation.translate((float)d.X, 0.0f, (float)d.Y); int targetSectorGroup = 0; auto portalDestination = sector->GetPortal(plane)->mDestination; @@ -1089,8 +1097,9 @@ void DoomLevelMesh::CreatePortals(FLevelLocals& doomMap) z = tz - sz; } - transformation.rotate((float)sourceLine->getPortalAngleDiff().Degrees(), 0.0f, 0.0f, 1.0f); - transformation.translate((float)(targetXYZ.X - sourceXYZ.X), (float)(targetXYZ.Y - sourceXYZ.Y), (float)z); + // Note: Y and Z is swapped in the shader due to how the hwrenderer was implemented + transformation.rotate((float)sourceLine->getPortalAngleDiff().Degrees(), 0.0f, 1.0f, 0.0f); + transformation.translate((float)(targetXYZ.X - sourceXYZ.X), (float)z, (float)(targetXYZ.Y - sourceXYZ.Y)); int targetSectorGroup = 0; if (auto sector = targetLine->frontsector ? targetLine->frontsector : targetLine->backsector) diff --git a/src/rendering/hwrenderer/doom_levelmesh.h b/src/rendering/hwrenderer/doom_levelmesh.h index 44d8c82dc..29e85e7b9 100644 --- a/src/rendering/hwrenderer/doom_levelmesh.h +++ b/src/rendering/hwrenderer/doom_levelmesh.h @@ -87,7 +87,7 @@ private: int GetSampleDimension(const DoomLevelMeshSurface& surf); void CreatePortals(FLevelLocals& doomMap); - FLightNode* GetSurfaceLightNode(const DoomLevelMeshSurface* doomsurf); + std::pair GetSurfaceLightNode(const DoomLevelMeshSurface* doomsurf); TArray Sides; TArray Flats; diff --git a/wadsrc/static/shaders/lightmap/polyfill_rayquery.glsl b/wadsrc/static/shaders/lightmap/polyfill_rayquery.glsl index 2595ab237..97f61434c 100644 --- a/wadsrc/static/shaders/lightmap/polyfill_rayquery.glsl +++ b/wadsrc/static/shaders/lightmap/polyfill_rayquery.glsl @@ -1,8 +1,17 @@ +struct TraceResult +{ + float t; + vec3 primitiveWeights; + int primitiveIndex; +}; + #if defined(USE_RAYQUERY) -int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, out float t, out vec3 primitiveWeights) +TraceResult TraceFirstHit(vec3 origin, float tmin, vec3 dir, float tmax) { + TraceResult result; + rayQueryEXT rayQuery; rayQueryInitializeEXT(rayQuery, acc, gl_RayFlagsCullBackFacingTrianglesEXT, 0xFF, origin, tmin, dir, tmax); @@ -16,18 +25,20 @@ int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, if (rayQueryGetIntersectionTypeEXT(rayQuery, true) == gl_RayQueryCommittedIntersectionTriangleEXT) { - t = rayQueryGetIntersectionTEXT(rayQuery, true); + result.t = rayQueryGetIntersectionTEXT(rayQuery, true); - primitiveWeights.xy = rayQueryGetIntersectionBarycentricsEXT(rayQuery, true); - primitiveWeights.z = 1.0 - primitiveWeights.x - primitiveWeights.y; + result.primitiveWeights.xy = rayQueryGetIntersectionBarycentricsEXT(rayQuery, true); + result.primitiveWeights.z = 1.0 - result.primitiveWeights.x - result.primitiveWeights.y; - return rayQueryGetIntersectionPrimitiveIndexEXT(rayQuery, true); + result.primitiveIndex = rayQueryGetIntersectionPrimitiveIndexEXT(rayQuery, true); } else { - t = tmax; - return -1; + result.t = tmax; + result.primitiveIndex = -1; } + + return result; } /* @@ -234,8 +245,10 @@ TraceHit find_first_hit(RayBBox ray) return hit; } -int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, out float tparam, out vec3 primitiveWeights) +TraceResult TraceFirstHit(vec3 origin, float tmin, vec3 dir, float tmax) { + TraceResult result; + // Perform segmented tracing to keep the ray AABB box smaller vec3 ray_start = origin; vec3 ray_end = origin + dir * tmax; @@ -251,16 +264,17 @@ int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, TraceHit hit = find_first_hit(ray); if (hit.fraction < 1.0) { - tparam = hit.fraction = segstart * (1.0 - hit.fraction) + segend * hit.fraction; - primitiveWeights.x = hit.b; - primitiveWeights.y = hit.c; - primitiveWeights.z = 1.0 - hit.b - hit.c; - return hit.triangle; + result.t = mix(segstart, segend, hit.fraction); + result.primitiveWeights.x = hit.b; + result.primitiveWeights.y = hit.c; + result.primitiveWeights.z = 1.0 - hit.b - hit.c; + result.primitiveIndex = hit.triangle; } } - tparam = tracedist; - return -1; + result.t = tracedist; + result.primitiveIndex = -1; + return result; } #endif diff --git a/wadsrc/static/shaders/lightmap/trace_ambient_occlusion.glsl b/wadsrc/static/shaders/lightmap/trace_ambient_occlusion.glsl index 2f10ea1d1..be15319b3 100644 --- a/wadsrc/static/shaders/lightmap/trace_ambient_occlusion.glsl +++ b/wadsrc/static/shaders/lightmap/trace_ambient_occlusion.glsl @@ -20,14 +20,19 @@ float TraceAmbientOcclusion(vec3 origin, vec3 normal) vec3 H = normalize(vec3(Xi.x * 2.0f - 1.0f, Xi.y * 2.0f - 1.0f, 1.5 - length(Xi))); vec3 L = H.x * tangent + H.y * bitangent + H.z * N; - float hitDistance; - int primitiveID = TraceFirstHitTriangleT(origin, minDistance, L, aoDistance, hitDistance); - if (primitiveID != -1) + TraceResult result = TraceFirstHit(origin, minDistance, L, aoDistance); + + // Ignore surfaces with textures, skies or portals + if (result.primitiveIndex != -1) { - SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; - if (surface.Sky == 0.0) + SurfaceInfo surface = GetSurface(result.primitiveIndex); + if (surface.Sky == 0.0 && surface.TextureIndex == 0 && surface.PortalIndex == 0) { - ambience += clamp(hitDistance / aoDistance, 0.0, 1.0); + ambience += clamp(result.t / aoDistance, 0.0, 1.0); + } + else + { + ambience += 1.0; } } else diff --git a/wadsrc/static/shaders/lightmap/trace_levelmesh.glsl b/wadsrc/static/shaders/lightmap/trace_levelmesh.glsl index 7bae42b48..bedbb9478 100644 --- a/wadsrc/static/shaders/lightmap/trace_levelmesh.glsl +++ b/wadsrc/static/shaders/lightmap/trace_levelmesh.glsl @@ -1,126 +1,38 @@ -vec4 rayColor; - -vec4 alphaBlend(vec4 a, vec4 b); vec4 BeerLambertSimple(vec4 medium, vec4 ray_color); -vec4 blend(vec4 a, vec4 b); -int TraceFirstHitTriangleT(vec3 origin, float tmin, vec3 dir, float tmax, out float t) +SurfaceInfo GetSurface(int primitiveIndex) { - int primitiveID = -1; - vec3 primitiveWeights; - for (int i = 0; i < 4; i++) + return surfaces[surfaceIndices[primitiveIndex]]; +} + +vec2 GetSurfaceUV(int primitiveIndex, vec3 primitiveWeights) +{ + int index = primitiveIndex * 3; + return + vertices[elements[index + 1]].uv * primitiveWeights.x + + vertices[elements[index + 2]].uv * primitiveWeights.y + + vertices[elements[index + 0]].uv * primitiveWeights.z; +} + +vec4 BlendTexture(SurfaceInfo surface, vec2 uv, vec4 rayColor) +{ + if (surface.TextureIndex == 0) { - primitiveID = TraceFirstHitTriangleNoPortal(origin, tmin, dir, tmax, t, primitiveWeights); - - if(primitiveID < 0) - { - break; - } - - SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; - - if(surface.PortalIndex == 0) - { - int index = primitiveID * 3; - vec2 uv = vertices[elements[index + 1]].uv * primitiveWeights.x + vertices[elements[index + 2]].uv * primitiveWeights.y + vertices[elements[index + 0]].uv * primitiveWeights.z; - - if (surface.TextureIndex == 0) - { - break; - } - - vec4 color = texture(textures[surface.TextureIndex], uv); - color.w *= surface.Alpha; - - if (color.w > 0.999 || all(lessThan(rayColor.rgb, vec3(0.001)))) - { - break; - } - - rayColor = blend(color, rayColor); - } - - // Portal was hit: Apply transformation onto the ray - mat4 transformationMatrix = portals[surface.PortalIndex].Transformation; - - origin = (transformationMatrix * vec4(origin + dir * t, 1.0)).xyz; - dir = (transformationMatrix * vec4(dir, 0.0)).xyz; - tmax -= t; + return rayColor; } - return primitiveID; -} - -int TraceFirstHitTriangle(vec3 origin, float tmin, vec3 dir, float tmax) -{ - float t; - return TraceFirstHitTriangleT(origin, tmin, dir, tmax, t); -} - -bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax) -{ - return TraceFirstHitTriangle(origin, tmin, dir, tmax) >= 0; -} - -bool TracePoint(vec3 origin, vec3 target, float tmin, vec3 dir, float tmax) -{ - int primitiveID; - float t; - vec3 primitiveWeights; - for (int i = 0; i < 4; i++) + else { - t = tmax; - primitiveID = TraceFirstHitTriangleNoPortal(origin, tmin, dir, tmax, t, primitiveWeights); - - origin += dir * t; - tmax -= t; - - if(primitiveID < 0) - { - // We didn't hit anything - break; - } - - SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; - - if (surface.PortalIndex == 0) - { - int index = primitiveID * 3; - vec2 uv = vertices[elements[index + 1]].uv * primitiveWeights.x + vertices[elements[index + 2]].uv * primitiveWeights.y + vertices[elements[index + 0]].uv * primitiveWeights.z; - - if (surface.TextureIndex == 0) - { - break; - } - - vec4 color = texture(textures[surface.TextureIndex], uv); - color.w *= surface.Alpha; - - if (color.w > 0.999 || all(lessThan(rayColor.rgb, vec3(0.001)))) - { - break; - } - - rayColor = blend(color, rayColor); - } - - if(dot(surface.Normal, dir) >= 0.0) - { - continue; - } - - mat4 transformationMatrix = portals[surface.PortalIndex].Transformation; - origin = (transformationMatrix * vec4(origin, 1.0)).xyz; - dir = (transformationMatrix * vec4(dir, 0.0)).xyz; + vec4 color = texture(textures[surface.TextureIndex], uv); + return BeerLambertSimple(vec4(1.0 - color.rgb, color.a * surface.Alpha), rayColor); } - - return distance(origin, target) <= 1.0; } -vec4 alphaBlend(vec4 a, vec4 b) +void TransformRay(uint portalIndex, inout vec3 origin, inout vec3 dir) { - float na = a.w + b.w * (1.0 - a.w); - return vec4((a.xyz * a.w + b.xyz * b.w * (1.0 - a.w)) / na, max(0.001, na)); + mat4 transformationMatrix = portals[portalIndex].Transformation; + origin = (transformationMatrix * vec4(origin, 1.0)).xyz; + dir = (transformationMatrix * vec4(dir, 0.0)).xyz; } vec4 BeerLambertSimple(vec4 medium, vec4 ray_color) // based on Beer-Lambert law @@ -129,9 +41,3 @@ vec4 BeerLambertSimple(vec4 medium, vec4 ray_color) // based on Beer-Lambert law ray_color.rgb *= exp(-medium.rgb * vec3(z)); return ray_color; } - -vec4 blend(vec4 a, vec4 b) -{ - return BeerLambertSimple(vec4(1.0 - a.rgb, a.w), b); -} - diff --git a/wadsrc/static/shaders/lightmap/trace_light.glsl b/wadsrc/static/shaders/lightmap/trace_light.glsl index 036556484..6d94ac0e0 100644 --- a/wadsrc/static/shaders/lightmap/trace_light.glsl +++ b/wadsrc/static/shaders/lightmap/trace_light.glsl @@ -1,4 +1,5 @@ +vec4 TracePointLightRay(vec3 origin, vec3 lightpos, float tmin, vec4 rayColor); vec2 getVogelDiskSample(int sampleIndex, int sampleCount, float phi); vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light, int surfaceIndex) @@ -27,6 +28,8 @@ vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light, int surfaceIndex) float attenuation = distAttenuation * angleAttenuation * spotAttenuation; if (attenuation > 0.0) { + vec4 rayColor = vec4(light.Color.rgb * (attenuation * light.Intensity), 1.0); + #if defined(USE_SOFTSHADOWS) vec3 v = (abs(dir.x) > abs(dir.y)) ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); @@ -40,19 +43,11 @@ vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light, int surfaceIndex) vec2 gridoffset = getVogelDiskSample(i, step_count, gl_FragCoord.x + gl_FragCoord.y * 13.37) * lightsize; vec3 pos = light.Origin + xdir * gridoffset.x + ydir * gridoffset.y; - rayColor = vec4(light.Color.rgb, 1.0); - if (TracePoint(origin, pos, minDistance, normalize(pos - origin), distance(origin, pos))) - { - incoming.rgb += (rayColor.rgb * rayColor.w) * (attenuation * light.Intensity) / float(step_count); - } + incoming.rgb += TracePointLightRay(origin, pos, minDistance, rayColor).rgb / float(step_count); } #else - rayColor = vec4(light.Color.rgb, 1.0); - if(TracePoint(origin, light.Origin, minDistance, dir, dist)) - { - incoming.rgb += (rayColor.rgb * rayColor.w) * (attenuation * light.Intensity); - } + incoming.rgb += TracePointLightRay(origin, light.Origin, minDistance, rayColor).rgb; #endif } } @@ -60,6 +55,38 @@ vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light, int surfaceIndex) return incoming; } +vec4 TracePointLightRay(vec3 origin, vec3 lightpos, float tmin, vec4 rayColor) +{ + vec3 dir = normalize(lightpos - origin); + float tmax = distance(origin, lightpos); + + for (int i = 0; i < 3; i++) + { + TraceResult result = TraceFirstHit(origin, tmin, dir, tmax); + + // We hit nothing. Point light is visible. + if (result.primitiveIndex == -1) + return rayColor; + + SurfaceInfo surface = GetSurface(result.primitiveIndex); + + // Blend with surface texture + rayColor = BlendTexture(surface, GetSurfaceUV(result.primitiveIndex, result.primitiveWeights), rayColor); + + // Stop if it isn't a portal, or there is no light left + if (surface.PortalIndex == 0 || rayColor.r + rayColor.g + rayColor.b <= 0.0) + return vec4(0.0); + + // Move to surface hit point + origin += dir * result.t; + tmax -= result.t; + + // Move through the portal + TransformRay(surface.PortalIndex, origin, dir); + } + return vec4(0.0); +} + vec2 getVogelDiskSample(int sampleIndex, int sampleCount, float phi) { const float goldenAngle = radians(180.0) * (3.0 - sqrt(5.0)); diff --git a/wadsrc/static/shaders/lightmap/trace_sunlight.glsl b/wadsrc/static/shaders/lightmap/trace_sunlight.glsl index 90d66161e..fd5187135 100644 --- a/wadsrc/static/shaders/lightmap/trace_sunlight.glsl +++ b/wadsrc/static/shaders/lightmap/trace_sunlight.glsl @@ -1,5 +1,6 @@ vec2 getVogelDiskSample(int sampleIndex, int sampleCount, float phi); +vec4 TraceSunRay(vec3 origin, float tmin, vec3 dir, float tmax, vec4 rayColor); vec3 TraceSunLight(vec3 origin, vec3 normal, int surfaceIndex) { @@ -15,6 +16,8 @@ vec3 TraceSunLight(vec3 origin, vec3 normal, int surfaceIndex) vec3 incoming = vec3(0.0); const float dist = 65536.0; + vec4 rayColor = vec4(SunColor.rgb * SunIntensity, 1.0); + #if defined(USE_SOFTSHADOWS) vec3 target = origin + SunDir * dist; @@ -29,29 +32,45 @@ vec3 TraceSunLight(vec3 origin, vec3 normal, int surfaceIndex) { vec2 gridoffset = getVogelDiskSample(i, step_count, gl_FragCoord.x + gl_FragCoord.y * 13.37) * lightsize; vec3 pos = target + xdir * gridoffset.x + ydir * gridoffset.y; - - rayColor = vec4(SunColor.rgb * SunIntensity, 1.0); - - int primitiveID = TraceFirstHitTriangle(origin, minDistance, normalize(pos - origin), dist); - if (primitiveID != -1) - { - SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; - incoming.rgb += rayColor.rgb * rayColor.w * surface.Sky / float(step_count); - } + incoming.rgb += TraceSunRay(origin, minDistance, normalize(pos - origin), dist, rayColor).rgb / float(step_count); } #else - rayColor = vec4(SunColor.rgb * SunIntensity, 1.0); - - int primitiveID = TraceFirstHitTriangle(origin, minDistance, SunDir, dist); - if (primitiveID != -1) - { - SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; - incoming.rgb = rayColor.rgb * rayColor.w * surface.Sky; - } + incoming.rgb = TraceSunRay(origin, minDistance, SunDir, dist, rayColor).rgb; #endif return incoming * angleAttenuation; } + +vec4 TraceSunRay(vec3 origin, float tmin, vec3 dir, float tmax, vec4 rayColor) +{ + for (int i = 0; i < 3; i++) + { + TraceResult result = TraceFirstHit(origin, tmin, dir, tmax); + + // We hit nothing. We have to hit a sky surface to hit the sky. + if (result.primitiveIndex == -1) + return vec4(0.0); + + SurfaceInfo surface = GetSurface(result.primitiveIndex); + + // Blend with surface texture + rayColor = BlendTexture(surface, GetSurfaceUV(result.primitiveIndex, result.primitiveWeights), rayColor); + + // Stop if it isn't a portal, or there is no light left + if (surface.PortalIndex == 0 || rayColor.r + rayColor.g + rayColor.b <= 0.0) + return rayColor * surface.Sky; + + // Move to surface hit point + origin += dir * result.t; + tmax -= result.t; + if (tmax <= tmin) + return vec4(0.0); + + // Move through the portal + TransformRay(surface.PortalIndex, origin, dir); + } + return vec4(0.0); +}