public static unsafe bool Hit(this BvhNode n, NativeArray <Entity> entities, Ray r, float tMin, float tMax, ref Random rng, AccumulateJob.WorkingArea wa, #if FULL_DIAGNOSTICS ref Diagnostics diagnostics, #endif out HitRecord rec) { int candidateCount = 0, nodeStackHeight = 1; BvhNode **nodeStackTail = wa.Nodes; Entity * candidateListTail = wa.Entities - 1, candidateListHead = wa.Entities; float3 rayInvDirection = rcp(r.Direction); *nodeStackTail = &n; while (nodeStackHeight > 0) { BvhNode *nodePtr = *nodeStackTail--; nodeStackHeight--; if (!nodePtr->Bounds.Hit(r.Origin, rayInvDirection, tMin, tMax)) { continue; } #if FULL_DIAGNOSTICS diagnostics.BoundsHitCount++; #endif if (nodePtr->IsLeaf) { *++candidateListTail = entities[nodePtr->EntityId]; candidateCount++; } else { *++nodeStackTail = nodePtr->Left; *++nodeStackTail = nodePtr->Right; nodeStackHeight += 2; } } #if FULL_DIAGNOSTICS diagnostics.CandidateCount = candidateCount; #endif if (candidateCount == 0) { rec = default; return(false); } #if BVH_SIMD // TODO: this is fully broken // skip SIMD codepath if there's only one if (candidateCount == 1) { return(candidateListHead->Hit(r, tMin, tMax, out rec)); } var simdSpheresHead = (Sphere4 *)wa.Vectors; int simdBlockCount = (int)ceil(candidateCount / 4.0f); float4 a = dot(r.Direction, r.Direction); int4 curId = int4(0, 1, 2, 3), hitId = -1; float4 hitT = tMax; Sphere4 *blockCursor = simdSpheresHead; Entity * candidateCursor = candidateListHead; int candidateIndex = 0; for (int i = 0; i < simdBlockCount; i++) { for (int j = 0; j < 4; j++) { if (candidateIndex < candidateCount) { Sphere *sphereData = candidateCursor->AsSphere; float3 center = sphereData->Center(r.Time); blockCursor->CenterX[j] = center.x; blockCursor->CenterY[j] = center.y; blockCursor->CenterZ[j] = center.z; blockCursor->SquaredRadius[j] = sphereData->SquaredRadius; ++candidateCursor; ++candidateIndex; } else { blockCursor->CenterX[j] = float.MaxValue; blockCursor->CenterY[j] = float.MaxValue; blockCursor->CenterZ[j] = float.MaxValue; blockCursor->SquaredRadius[j] = 0; } } float4 ocX = r.Origin.x - blockCursor->CenterX, ocY = r.Origin.y - blockCursor->CenterY, ocZ = r.Origin.z - blockCursor->CenterZ; float4 b = ocX * r.Direction.x + ocY * r.Direction.y + ocZ * r.Direction.z; float4 c = ocX * ocX + ocY * ocY + ocZ * ocZ - blockCursor->SquaredRadius; float4 discriminant = b * b - a * c; bool4 discriminantTest = discriminant > 0; if (any(discriminantTest)) { float4 sqrtDiscriminant = sqrt(discriminant); float4 t0 = (-b - sqrtDiscriminant) / a; float4 t1 = (-b + sqrtDiscriminant) / a; float4 t = select(t1, t0, t0 > tMin); bool4 mask = discriminantTest & t > tMin & t < hitT; hitId = select(hitId, curId, mask); hitT = select(hitT, t, mask); } curId += 4; ++blockCursor; } if (all(hitId == -1)) { rec = default; return(false); } float minDistance = cmin(hitT); int laneMask = bitmask(hitT == minDistance); int firstLane = tzcnt(laneMask); int closestId = hitId[firstLane]; Sphere *closestSphere = candidateListHead[closestId].AsSphere; float3 point = r.GetPoint(minDistance); rec = new HitRecord(minDistance, point, (point - closestSphere->Center(r.Time)) / closestSphere->Radius, closestSphere->Material); return(true); #else // iterative candidate tests (non-SIMD) bool anyHit = candidateListHead->Hit(r, tMin, tMax, ref rng, out rec); for (int i = 1; i < candidateCount; i++) { bool thisHit = candidateListHead[i].Hit(r, tMin, tMax, ref rng, out HitRecord thisRec); if (thisHit && (!anyHit || thisRec.Distance < rec.Distance)) { anyHit = true; rec = thisRec; } } if (anyHit) { return(true); } rec = default; return(false); #endif }
public bool Scatter(Ray ray, HitRecord rec, ref Random rng, PerlinData perlinData, out float3 reflectance, out Ray scattered) { switch (Type) { case MaterialType.Lambertian: { reflectance = Texture.Value(rec.Point, rec.Normal, TextureScale, perlinData); float3 randomDirection = rng.OnCosineWeightedHemisphere(rec.Normal); scattered = new Ray(rec.Point, randomDirection, ray.Time); return(true); } case MaterialType.Metal: { float3 outgoingDirection = WorldToTangentSpace(-ray.Direction, rec.Normal); if (GgxMicrofacet.ImportanceSample(Texture.Value(rec.Point, rec.Normal, TextureScale, perlinData), Roughness, ref rng, outgoingDirection, out float3 toLight, out reflectance)) { float3 scatterDirection = TangentToWorldSpace(toLight, rec.Normal); scattered = new Ray(rec.Point, scatterDirection, ray.Time); return(true); } scattered = default; return(false); } case MaterialType.Dielectric: { float3 reflected = reflect(ray.Direction, rec.Normal); reflectance = 1; float niOverNt; float3 outwardNormal; float cosine; if (dot(ray.Direction, rec.Normal) > 0) { outwardNormal = -rec.Normal; niOverNt = RefractiveIndex; cosine = RefractiveIndex * dot(ray.Direction, rec.Normal); } else { outwardNormal = rec.Normal; niOverNt = 1 / RefractiveIndex; cosine = -dot(ray.Direction, rec.Normal); } if (Refract(ray.Direction, outwardNormal, niOverNt, out float3 refracted)) { float reflectProb = Schlick(cosine, RefractiveIndex); scattered = new Ray(rec.Point, rng.NextFloat() < reflectProb ? reflected : refracted, ray.Time); } else { scattered = new Ray(rec.Point, reflected, ray.Time); } return(true); } case MaterialType.ProbabilisticVolume: scattered = new Ray(rec.Point, rng.NextFloat3Direction()); reflectance = Texture.Value(rec.Point, rec.Normal, TextureScale, perlinData); return(true); default: reflectance = default; scattered = default; return(false); } }
// iterative entity array hit test public static bool Hit(this NativeArray <Entity> entities, Ray r, float tMin, float tMax, ref Random rng, out HitRecord rec) { bool hitAnything = false; rec = new HitRecord(tMax, 0, 0, default); for (var i = 0; i < entities.Length; i++) { if (entities[i].Hit(r, tMin, rec.Distance, ref rng, out HitRecord thisRec)) { hitAnything = true; rec = thisRec; } } return(hitAnything); }
public unsafe void Sample(Ray materialScatterRay, float3 outgoingLightDirection, HitRecord rec, Material material, ref Random rng, out Ray scatterRay, out float pdfValue, out int?targetEntityId) { int totalOptions = TargetEntities.Length + (Mode == ImportanceSamplingMode.Mixture ? 1 : 0); int chosenOption = rng.NextInt(0, totalOptions); if (chosenOption == TargetEntities.Length) { scatterRay = materialScatterRay; targetEntityId = null; } else { Entity *chosenEntity = (Entity *)TargetEntities.GetUnsafeReadOnlyPtr() + chosenOption; float3 pointOnEntity = chosenEntity->RandomPoint(materialScatterRay.Time, ref rng); scatterRay = new Ray(materialScatterRay.Origin, normalize(pointOnEntity - materialScatterRay.Origin)); targetEntityId = chosenEntity->Id; } pdfValue = 0; if (Mode == ImportanceSamplingMode.Mixture) { pdfValue += material.Pdf(scatterRay.Direction, outgoingLightDirection, rec.Normal); } var basePointer = (Entity *)TargetEntities.GetUnsafeReadOnlyPtr(); for (int i = 0; i < TargetEntities.Length; i++) { pdfValue += (basePointer + i)->Pdf(scatterRay, ref rng); } pdfValue /= totalOptions; }