void DrawLine(float3 _start, float3 _end, float3 _ortho, float _thickness, float4 _color) { if (_thickness <= 0.0f) { // Compute the world size of a pixel for the farthest extremity of the line float3 wsCameraPos = (float3)m_Camera.World2Camera.GetRow(3); float3 wsDir = _end - _start; float t = -wsDir.Dot(_start - wsCameraPos) / wsDir.Dot(wsDir); t = Math.Max(0.0f, Math.Min(1.0f, t)); float3 wsNearest = _start + t * wsDir; float4 csNearest = new float4(wsNearest, 1) * m_Camera.World2Camera; float worldSize = 2.0f * (float)Math.Tan(0.5f * m_Camera.PerspectiveFOV) * csNearest.z; _thickness *= -worldSize / panelOutput.Height; } m_CB_Line.m._wsPosition0 = _start; m_CB_Line.m._thickness = _thickness; m_CB_Line.m._wsPosition1 = _end; m_CB_Line.m._wsOrtho = _ortho; m_CB_Line.m._color = _color; m_CB_Line.UpdateData(); m_Shader_Line.Use(); m_Prim_Quad.Render(m_Shader_Line); }
float DistBox(float3 _wsPosition, float3 _wsBoxCenter, float3 _wsBoxSize, float _rotationAngle) { float s = (float)Math.Sin(_rotationAngle); float c = (float)Math.Cos(_rotationAngle); float3 rotatedX = new float3(c, 0.0f, s); float3 rotatedZ = new float3(-s, 0.0f, c); _wsPosition -= _wsBoxCenter; _wsPosition = new float3(_wsPosition.Dot(rotatedX), _wsPosition.y, _wsPosition.Dot(rotatedZ)); return(DistBox(_wsPosition, float3.Zero, _wsBoxSize)); }
public void InitializeCamera(float3 _Position, float3 _Target, float3 _Up) { // Build the camera matrix float3 At = _Target - _Position; if (At.Dot(At) > 1e-2f) { // Normal case m_CameraTargetDistance = At.Length; At /= m_CameraTargetDistance; } else { // Special bad case m_CameraTargetDistance = 0.01f; At = new float3(0.0f, 0.0f, -1.0f); } float3 Ortho = _Up.Cross(At).Normalized; float4x4 CameraMat = float4x4.Identity; CameraMat.r0 = new float4(Ortho, 0.0f); CameraMat.r1 = new float4(At.Cross(Ortho), 0.0f); CameraMat.r2 = new float4(At, 0.0f); CameraMat.r3 = new float4(_Position, 1.0f); CameraTransform = CameraMat; // Setup the normalized target distance m_NormalizedTargetDistance = NormalizeTargetDistance(m_CameraTargetDistance); }
const int SAMPLES_COUNT = 50; // Number of samples used to compute the average terms // compute the average direction of the BRDF public void ComputeAverageTerms(IBRDF _BRDF, ref float3 _tsView, float _alpha) { magnitude = 0.0; fresnel = 0.0; Z = float3.Zero; error = 0.0; double weight, pdf, eval; float3 tsLight = float3.Zero; float3 H = float3.Zero; for (int j = 0; j < SAMPLES_COUNT; ++j) { for (int i = 0; i < SAMPLES_COUNT; ++i) { float U1 = (i + 0.5f) / SAMPLES_COUNT; float U2 = (j + 0.5f) / SAMPLES_COUNT; // sample _BRDF.GetSamplingDirection(ref _tsView, _alpha, U1, U2, ref tsLight); // eval eval = _BRDF.Eval(ref _tsView, ref tsLight, _alpha, out pdf); if (pdf == 0.0f) { continue; } H = (_tsView + tsLight).Normalized; // accumulate weight = eval / pdf; if (double.IsNaN(weight)) { throw new Exception("NaN!"); } magnitude += weight; fresnel += weight * Math.Pow(1 - Math.Max(0.0f, _tsView.Dot(H)), 5.0); Z += (float)weight * tsLight; } } magnitude /= SAMPLES_COUNT * SAMPLES_COUNT; fresnel /= SAMPLES_COUNT * SAMPLES_COUNT; // Finish building the average TBN orthogonal basis Z.y = 0.0f; // clear y component, which should be zero with isotropic BRDFs float length = Z.Length; if (length > 0.0f) { Z /= length; } else { Z = float3.UnitZ; } X.Set(Z.z, 0, -Z.x); Y = float3.UnitY; }
public double Eval(ref float3 _tsView, ref float3 _tsLight, float _alpha, out double _pdf) { if (_tsView.z <= 0) { _pdf = 0; return(0); } _alpha = Mathf.Max(0.002f, _alpha); float3 H = (_tsView + _tsLight).Normalized; double NdotL = Math.Max(1e-8, _tsLight.z); double NdotV = Math.Max(1e-8, _tsView.z); double NdotH = H.z; double LdotH = Math.Max(1e-8, _tsLight.Dot(H)); // D double cosb2 = NdotH * NdotH; double m2 = _alpha * _alpha; double D = Math.Exp((cosb2 - 1.0) / (cosb2 * m2)) // exp( -tan(a)² / m² ) / (Math.PI * m2 * cosb2 * cosb2); // / (PI * m² * cos(a)^4) // masking/shadowing double G = Math.Min(1, 2.0 * NdotH * Math.Min(NdotV, NdotL) / LdotH); // fr = F(H) * G(V, L) * D(H) / (4 * (N.L) * (N.V)) double res = D * G / (4.0 * NdotV); // Full specular mico-facet model is F * D * G / (4 * NdotL * NdotV) but since we're fitting with the NdotL included, it gets nicely canceled out! // pdf = D(H) * (N.H) / (4 * (L.H)) _pdf = Math.Abs(D * NdotH / (4.0 * LdotH)); return(res); }
public void GetSamplingDirection(ref float3 _tsView, float _alpha, float _U1, float _U2, ref float3 _direction) { float phi = Mathf.TWOPI * _U1; float r = _alpha * Mathf.Sqrt(_U2 / (1.0f - _U2)); float3 H = new float3(r * Mathf.Cos(phi), r * Mathf.Sin(phi), 1.0f).Normalized; _direction = -_tsView + 2.0f * H * H.Dot(_tsView); }
public void GetSamplingDirection(ref float3 _tsView, float _alpha, float _U1, float _U2, ref float3 _direction) { float phi = Mathf.TWOPI * _U1; float cosTheta = 1.0f / Mathf.Sqrt(1 - _alpha * _alpha * Mathf.Log(Mathf.Max(1e-6f, _U2))); float sinTheta = Mathf.Sqrt(1 - cosTheta * cosTheta); float3 H = new float3(sinTheta * Mathf.Cos(phi), sinTheta * Mathf.Sin(phi), cosTheta); _direction = 2.0f * H.Dot(_tsView) * H - _tsView; // Mirror view direction }
public void GetSamplingDirection(ref float3 _tsView, float _alpha, float _U1, float _U2, ref float3 _direction) { // Ward NDF sampling (eqs. 6 & 7 from above paper) float tanTheta = _alpha * Mathf.Sqrt(-Mathf.Log(Mathf.Max(1e-6f, _U1))); float phi = _U2 * Mathf.TWOPI; float cosTheta = 1.0f / Mathf.Sqrt(1 + tanTheta * tanTheta); float sinTheta = Mathf.Sqrt(1 - cosTheta * cosTheta); float3 H = new float3(sinTheta * Mathf.Cos(phi), sinTheta * Mathf.Sin(phi), cosTheta); _direction = 2.0f * H.Dot(_tsView) * H - _tsView; // Mirror view direction }
private void timer1_Tick(object sender, EventArgs e) { if (!m_bHasRendered) { return; } m_bHasRendered = false; // Perform simulation int NeighborsCount = m_NeighborPositions.Length; // Compute pressure forces float F = 0.01f * floatTrackbarControlForce.Value; float3[] Forces = new float3[NeighborsCount]; for (int i = 0; i < NeighborsCount - 1; i++) { float3 D0 = m_NeighborPositions[i].Normalized; for (int j = i + 1; j < NeighborsCount; j++) { float3 D1 = m_NeighborPositions[j].Normalized; float3 Dir = (D1 - D0).Normalized; float Dot = D0.Dot(D1) - 1.0f; // in [0,-2] float Force = F * (float)Math.Exp(Dot); Forces[i] = Forces[i] - Force * Dir; // Pushes 0 away from 1 Forces[j] = Forces[j] + Force * Dir; // Pushes 1 away from 0 } } // Apply force for (int i = 0; i < NeighborsCount; i++) { float3 NewPosition = (m_NeighborPositions[i] + Forces[i]).Normalized; m_NeighborPositions[i] = NewPosition; m_SB_Neighbors.m[i].m_Position = NewPosition; } // Update m_SB_Neighbors.Write(); if (checkBoxRenderCell.Checked) { buttonBuildCell_Click(this, EventArgs.Empty); Application.DoEvents(); } }
public double Eval(ref float3 _tsView, ref float3 _tsLight, float _alpha, out double _pdf) { if (_tsView.z <= 0) { _pdf = 0; return(0); } // masking double lambdaV = Lambda(_tsView.z, _alpha); // shadowing double G2 = 0; if (_tsLight.z > 0.0f) { double lambdaL = Lambda(_tsLight.z, _alpha); G2 = 1.0 / (1.0 + lambdaV + lambdaL); } // D float3 H = _tsView + _tsLight; float lengthH = H.Length; if (lengthH > 1e-8f) { H = H / lengthH; } else { H = float3.UnitZ; } double slopex = H.x / H.z; double slopey = H.y / H.z; double D = 1.0 / (1.0 + (slopex * slopex + slopey * slopey) / _alpha / _alpha); D = D * D; D = D / (Math.PI * _alpha * _alpha * H.z * H.z * H.z * H.z); double res = D * G2 / 4.0 / _tsView.z; // Full specular mico-facet model is F * D * G / (4 * NdotL * NdotV) but since we're fitting with the NdotL included, it gets nicely canceled out! // pdf = D(H) * (N.H) / (4 * (L.H)) _pdf = Math.Abs(D * H.z / 4.0 / _tsView.Dot(H)); return(res); }
public double Eval(ref float3 _tsView, ref float3 _tsLight, float _alpha, out double _pdf) { if (_tsView.z <= 0) { _pdf = 0; return(0); } _alpha = Mathf.Max(0.002f, _alpha); double NdotL = Math.Max(0, _tsLight.z); double NdotV = Math.Max(0, _tsView.z); double LdotV = Math.Max(0, _tsLight.Dot(_tsView)); double perceptualRoughness = Math.Sqrt(_alpha); // (2 * LdotH * LdotH) = 1 + LdotV // real fd90 = 0.5 + 2 * LdotH * LdotH * perceptualRoughness; double fd90 = 0.5 + (perceptualRoughness + perceptualRoughness * LdotV); // Two schlick fresnel term double lightScatter = F_Schlick(1.0, fd90, NdotL); double viewScatter = F_Schlick(1.0, fd90, NdotV); // Normalize the BRDF for polar view angles of up to (Pi/4). // We use the worst case of (roughness = albedo = 1), and, for each view angle, // integrate (brdf * cos(theta_light)) over all light directions. // The resulting value is for (theta_view = 0), which is actually a little bit larger // than the value of the integral for (theta_view = Pi/4). // Hopefully, the compiler folds the constant together with (1/Pi). double res = lightScatter * viewScatter / Math.PI; res /= 1.03571; // Remember we must include the N.L term! res *= NdotL; //res = NdotL / Math.PI; // Lambert test // Cosine-weighted hemisphere sampling _pdf = NdotL / Math.PI; return(res); }
public double Eval(ref float3 _tsView, ref float3 _tsLight, float _alpha, out double _pdf) { if (_tsView.z <= 0) { _pdf = 0; return(0); } // _alpha = Mathf.Max( 0.002f, _alpha ); // masking double lambdaV = Lambda(_tsView.z, _alpha); // double G1 = 1.0 / (1.0 + lambdaV); // shadowing double G2 = 0; if (_tsLight.z > 0.0f) { double lambdaL = Lambda(_tsLight.z, _alpha); G2 = 1.0 / (1.0 + lambdaV + lambdaL); } // D float3 H = (_tsView + _tsLight).Normalized; // H.z = Mathf.Max( 1e-8f, H.z ); // if ( Mathf.Almost( H.z, 0, 1e-8f ) ) // H.z = 1e-8f; double slopex = H.x / H.z; double slopey = H.y / H.z; double D = 1.0 / (1.0 + (slopex * slopex + slopey * slopey) / _alpha / _alpha); D = D * D; D = D / (Math.PI * _alpha * _alpha * H.z * H.z * H.z * H.z); double res = D * G2 / 4.0 / _tsView.z; // Full specular mico-facet model is F * D * G / (4 * NdotL * NdotV) but since we're fitting with the NdotL included, it gets nicely canceled out! // pdf = D(H) * (N.H) / (4 * (L.H)) _pdf = Math.Abs(D * H.z / 4.0 / _tsView.Dot(H)); return(res); }
// Cut polygon with a new plane, yielding a new polygon public void Cut(float3 _P, float3 _N) { List <float3> NewVertices = new List <float3>(); for (int EdgeIndex = 0; EdgeIndex < m_Vertices.Length; EdgeIndex++) { float3 P0 = m_Vertices[EdgeIndex + 0]; float3 P1 = m_Vertices[(EdgeIndex + 1) % m_Vertices.Length]; float Dot0 = (P0 - _P).Dot(_N); float Dot1 = (P1 - _P).Dot(_N); bool InFront0 = Dot0 >= 0.0f; bool InFront1 = Dot1 >= 0.0f; if (!InFront0 && !InFront1) { continue; // This edge is completely behind the cutting plane, skip it entirely } if (InFront0 && InFront1) { // This edge is completely in front of the cutting plane, add P1 NewVertices.Add(P1); } else { // The edge intersects the plane float3 D = P1 - P0; float t = -Dot0 / D.Dot(_N); float3 I = P0 + t * D; NewVertices.Add(I); // Add intersection no matter what if (InFront1) { NewVertices.Add(P1); // Since the edge is entering the plane, also add end point } } } m_Vertices = NewVertices.ToArray(); }
public double Eval(ref float3 _tsView, ref float3 _tsLight, float _alpha, out double _pdf) { if (_tsView.z <= 0) { _pdf = 0; return(0); } _alpha = Mathf.Max(0.002f, _alpha); float3 H = (_tsView + _tsLight).Normalized; double NdotL = Math.Max(1e-8, _tsLight.z); double NdotV = _tsView.z; double NdotH = Math.Max(1e-8, H.z); double LdotH = Math.Max(1e-8, _tsLight.Dot(H)); // D (basically a Beckmann distribution + an additional divider for albedo bounding) double m2 = _alpha * _alpha; double cosb2 = NdotH * NdotH; double D = Math.Exp(-(1 - cosb2) / (m2 * cosb2)) // exp( -tan(a)² / m² ) / (Math.PI * m2 * cosb2 * cosb2); // / (PI * m² * cos(a)^4) D /= 4.0 * LdotH * LdotH; // Moroder // fr = F(H) * D(H) double res = D; // Remember we must include the N.L term! res *= NdotL; // From Walter, eq. 24 we know that pdf(H) = D(H) * (N.H) _pdf = Math.Abs(D * NdotH); // double weight = 2 * NdotL / (NdotL + NdotV); // Eq. 21 from Moroder paper // _pdf = D / weight; return(res); }
/// <summary> /// Computes up to 20 orders of A coefficients for various AO and angle values /// </summary> void NumericalIntegration_20Orders() { // Generate a bunch of rays with equal probability on the hemisphere const int THETA_SAMPLES = 100; const int SAMPLES_COUNT = 4 * THETA_SAMPLES * THETA_SAMPLES; const double dPhi = 2.0 * Math.PI / (4 * THETA_SAMPLES); float3[] directions = new float3[SAMPLES_COUNT]; for (int Y = 0; Y < THETA_SAMPLES; Y++) { for (int X = 0; X < 4 * THETA_SAMPLES; X++) { double phi = dPhi * (X + SimpleRNG.GetUniform()); double theta = 2.0 * Math.Acos(Math.Sqrt(1.0 - 0.5 * (Y + SimpleRNG.GetUniform()) / THETA_SAMPLES)); // Uniform sampling on theta directions[4 * THETA_SAMPLES * Y + X].Set((float)(Math.Sin(theta) * Math.Cos(phi)), (float)(Math.Sin(theta) * Math.Sin(phi)), (float)Math.Cos(theta)); } } // Compute numerical integration for various sets of angles const int TABLE_SIZE = 64; const int ORDERS = 20; float3 coneDirection = float3.Zero; float[,,] integratedSHCoeffs = new float[TABLE_SIZE, TABLE_SIZE, ORDERS]; double[] A = new double[ORDERS]; for (int thetaIndex = 0; thetaIndex < TABLE_SIZE; thetaIndex++) { float V = (float)thetaIndex / TABLE_SIZE; // float cosTheta = (float) Math.Cos( 0.5 * Math.PI * V ); float cosTheta = V; coneDirection.x = (float)Math.Sqrt(1.0f - cosTheta * cosTheta); coneDirection.z = cosTheta; for (int AOIndex = 0; AOIndex < TABLE_SIZE; AOIndex++) { float U = (float)AOIndex / TABLE_SIZE; // float cosConeHalfAngle = U; float cosConeHalfAngle = (float)Math.Cos(0.5 * Math.PI * U); Array.Clear(A, 0, ORDERS); for (int sampleIndex = 0; sampleIndex < SAMPLES_COUNT; sampleIndex++) { float3 direction = directions[sampleIndex]; if (direction.Dot(coneDirection) < cosConeHalfAngle) { continue; // Sample is outside cone } float u = direction.z; // cos(theta_sample) for (int order = 0; order < ORDERS; order++) { A[order] += u * SHFunctions.P0(order, u); } } // Finalize integration for (int order = 0; order < ORDERS; order++) { A[order] *= 2.0 * Math.PI / SAMPLES_COUNT; } for (int order = 0; order < ORDERS; order++) { integratedSHCoeffs[thetaIndex, AOIndex, order] = (float)A[order]; } } } // Save table using (System.IO.FileStream S = new System.IO.FileInfo(@"ConeTable_cosTheta_order20.float").Create()) using (System.IO.BinaryWriter W = new System.IO.BinaryWriter(S)) { for (int thetaIndex = 0; thetaIndex < TABLE_SIZE; thetaIndex++) { for (int AOIndex = 0; AOIndex < TABLE_SIZE; AOIndex++) { for (int order = 0; order < ORDERS; order++) { W.Write(integratedSHCoeffs[thetaIndex, AOIndex, order]); } } } } }
// Samples the irradiance reflected from a screen-space position located around the center pixel position // _ssPosition, the screen-space position to sample // _H0, the center height // _radius_m, the radius (in meters) from the center position // _integralFactors, some pre-computed factors to feed the integral // _maxCos, the floating maximum cos(theta) that indicates the angle of the perceived horizon // _optionnal_centerRho, the reflectance of the center pixel (fallback only necessary if no albedo map is available and if it's only irradiance that is stored in the source irradiance map instead of radiance, in which case albedo is already pre-mutliplied) // float3 SampleIrradiance(float2 _csPosition, float4x4 _localCamera2World, float _Z0, float2 _mipLevel, float2 _integralFactors, ref float3 _previousRadiance, ref float _maxCos) { // Transform camera-space position into screen space float3 wsNeighborPosition = _localCamera2World[3].xyz + _csPosition.x * _localCamera2World[0].xyz + _csPosition.y * _localCamera2World[1].xyz; // float3 deltaPos = wsNeighborPosition = _Camera2World[3].xyz; // float2 gcsPosition = float3( dot( deltaPos, _Camera2World[0].xyz ), dot( deltaPos, _Camera2World[1].xyz ), dot( deltaPos, _Camera2World[2].xyz ) ); // #TODO: Optimize! float4 projPosition = new float4(wsNeighborPosition, 1.0f) * m_world2Proj; projPosition /= projPosition.w; float2 UV = new float2(0.5f * (1.0f + projPosition.x), 0.5f * (1.0f - projPosition.y)); float2 ssPosition = UV * m_resolution; // Sample new depth and rebuild final world-space position float neighborZ = FetchDepth(ssPosition, _mipLevel.x); float3 wsView = wsNeighborPosition - m_camera2World[3].xyz; // Neighbor world-space position (not projected), relative to camera wsView /= wsView.Dot(m_camera2World[2].xyz); // Scaled so its length against the camera's Z axis is 1 wsView *= neighborZ; // Scaled again so its length agains the camera's Z axis equals our sampled Z wsNeighborPosition = m_camera2World[3].xyz + wsView; // Final reprojected world-space position // Update horizon angle following eq. (3) from the paper wsNeighborPosition -= _localCamera2World[3].xyz; // Neighbor position, relative to central position float3 csNeighborPosition = new float3(wsNeighborPosition.Dot(_localCamera2World[0].xyz), wsNeighborPosition.Dot(_localCamera2World[1].xyz), wsNeighborPosition.Dot(_localCamera2World[2].xyz)); float radius = csNeighborPosition.xy.Length; float d = csNeighborPosition.z; //float bilateralWeight = BilateralFilterDepth( _Z0, neighborZ, radius ); // Attenuate //d *= bilateralWeight; float cosHorizon = d / Mathf.Sqrt(radius * radius + d * d); // Cosine to horizon angle if (cosHorizon <= _maxCos) { return(float3.Zero); // Below the horizon... No visible contribution. } #if SAMPLE_NEIGHBOR_RADIANCE // NOW USELESS I THINK... // // Sample neighbor's incoming radiance value, only if difference in depth is not too large // float bilateralWeight = BilateralFilterRadiance( _H0, neighborH, _radius ); // if ( bilateralWeight > 0.0 ) // _previousRadiance = lerp( _previousRadiance, FetchRadiance( _ssPosition ), bilateralWeight ); // Accept new height and its radiance value // Sample always (actually, it's okay now we accepted the height through the first bilateral filter earlier) _previousRadiance = FetchRadiance(ssPosition, _mipLevel.y); #endif // Integrate over horizon difference (always from smallest to largest angle otherwise we get negative results!) float3 incomingRadiance = _previousRadiance * IntegrateSolidAngle(_integralFactors, cosHorizon, _maxCos); // #TODO: Integrate with linear interpolation of irradiance as well?? // #TODO: Integrate with Fresnel F0! _maxCos = cosHorizon; // Register a new positive horizon return(incomingRadiance); }
/// <summary> /// Computes the solid angle covered by the light as seen by provided position /// </summary> /// <param name="_wsPosition"></param> void ComputeResult(float3 _wsPosition) { float3 lsLightPos = m_CB_Light.m._wsLight2World[3].xyz - _wsPosition; float3 lsLightX = m_CB_Light.m._wsLight2World[0].w * m_CB_Light.m._wsLight2World[0].xyz; float3 lsLightY = m_CB_Light.m._wsLight2World[1].w * m_CB_Light.m._wsLight2World[1].xyz; float3 n = float3.UnitY; // Plane normal is straight up float dA = Mathf.TWOPI / 1024.0f; float c0, s0, c1, s1; float FdotN, dot; double dTheta; float3 v0, v1, u0, u1, cross, dF; double sum0 = 0; double sum1 = 0; float3 sumF0 = float3.Zero; float3 sumF1 = float3.Zero; for (int i = 0; i < 1024; i++) { c0 = Mathf.Cos(i * dA); s0 = Mathf.Sin(i * dA); c1 = Mathf.Cos(i * dA + dA); s1 = Mathf.Sin(i * dA + dA); v0 = lsLightPos + c0 * lsLightX + s0 * lsLightY; v1 = lsLightPos + c1 * lsLightX + s1 * lsLightY; u0 = v0.NormalizedSafe; u1 = v1.NormalizedSafe; dot = u0.Dot(u1); dTheta = Math.Acos(Mathf.Clamp(dot, -1, 1)); ////////////////////////////////////////////////////////////////////////// // Regular way cross = v0.Cross(v1); cross.NormalizeSafe(); // Directly accumulate F dF = (float)dTheta * cross; sumF0 += dF; // Accumulate F.n each step of the way // FdotN = dF.Dot( n ); FdotN = dF.y; sum0 += FdotN; ////////////////////////////////////////////////////////////////////////// // Bent way // FdotN = Mathf.Sqrt( 1 - Mathf.Pow2( u0.Dot( n ) ) ); // sin( alpha ) FdotN = Mathf.Sqrt(1 - Mathf.Pow2(u0.y)); // sin( alpha ) dF = (float)dTheta * FdotN * u0; sumF1 += dF; sum1 += (float)dTheta * FdotN; } textBoxResults.Text = "Target = " + _wsPosition + "\r\n" + "\r\n" + "Regular F = " + sumF0 + "\r\n" + "Regular sum = " + sum0 + "\r\n" + "Regular F.N = " + sumF0.Dot(n) + "\r\n" + "\r\n" + "Ortho F = " + sumF1 + "\r\n" + "Ortho sum = " + sum1 + "\r\n" + "Ortho |F| = " + sumF1.Length + " <== \r\n"; }
/// <summary> /// This code tests the determinant of the transform matrices that rectify a /// </summary> void CheckMatrix() { const int COUNT_RADIUS = 10; const int COUNT_X = 10; const int COUNT_Y = 10; const int COUNT_Z = 10; const float Xmax = 10.0f; const float Ymax = 10.0f; const float Zmax = 4.0f; const float Rb = 2.0f; float3 P0 = float3.Zero; float3 T = new float3(1, 0, 0); float3 B = new float3(0, 1, 0); float3 N = new float3(0, 0, 1); // float3 Nt, Nb, Tn, Bn, Dtn, Dbn; // float4 P = float4.UnitW, Pt; // float4x4 M = new float4x4(); float3 P, Pt; float3x3 M = new float3x3(); float3x3 M2 = new float3x3(); float3x3 invM = new float3x3(); float3x3 invM2 = new float3x3(); float det, illuminance; // float depth; List <float[, , ]> determinantss = new List <float[, , ]>(COUNT_RADIUS); // for ( int radiusIndex=0; radiusIndex < COUNT_RADIUS; radiusIndex++ ) int radiusIndex = 10; { float[,,] determinants = new float[COUNT_Z, 1 + 2 * COUNT_Y, 1 + 2 * COUNT_X]; determinantss.Add(determinants); float radiusFactor = Mathf.Max(0.01f, 2.0f * radiusIndex / 100); float Rt = radiusFactor * Rb; T.Set(Rt, 0, 0); B.Set(0, Rb, 0); for (int Z = COUNT_Z; Z > 0; Z--) { P0.z = Zmax * Z / COUNT_Z; for (int Y = -COUNT_Y; Y <= COUNT_Y; Y++) { P0.y = Ymax * Y / COUNT_Y; for (int X = -COUNT_X; X <= COUNT_X; X++) { P0.x = Xmax * X / COUNT_X; #if true // Matrix is a simple slanted parallelogram M.r0 = (T - (P0.Dot(T) / P0.Dot(N)) * N) / (Rt * Rt); M.r1 = (B - (P0.Dot(B) / P0.Dot(N)) * N) / (Rb * Rb); M.r2 = N / P0.Dot(N); invM = M.Inverse; det = M.Determinant; // Construct inverse directly invM2.r0.Set(T.x, B.x, P0.x); invM2.r1.Set(T.y, B.y, P0.y); invM2.r2.Set(T.z, B.z, P0.z); M2 = invM2.Inverse; // Test matrix is working P = P0; Pt = M * P; P = T; Pt = M * P; P = -T; Pt = M * P; P = B; Pt = M * P; P = -B; Pt = M * P; P = float3.Lerp(0.5f * T - 0.25f * B, 0.5f * T - 0.25f * B + P0, 0.666f); Pt = M * P; // invM *= M; // invM2 *= M2; // Test lighting computation // Build rectangular area light corners in local space float3 lsAreaLightPosition = P0; float3[] lsLightCorners = new float3[4]; lsLightCorners[0] = lsAreaLightPosition + T + B; lsLightCorners[1] = lsAreaLightPosition + T - B; lsLightCorners[2] = lsAreaLightPosition - T - B; lsLightCorners[3] = lsAreaLightPosition - T + B; float3x3 world2TangentSpace = float3x3.Identity; // Assume we're already in tangent space // Transform them into tangent-space float3[] tsLightCorners = new float3[4]; tsLightCorners[0] = lsLightCorners[0] * world2TangentSpace; tsLightCorners[1] = lsLightCorners[1] * world2TangentSpace; tsLightCorners[2] = lsLightCorners[2] * world2TangentSpace; tsLightCorners[3] = lsLightCorners[3] * world2TangentSpace; // Compute diffuse disk illuminance = DiskIrradiance(tsLightCorners); #else // Stupid version where I still thought we needed a perspective projection matrix! depth = P0.Dot(N); // Altitude from plane Nt = P0.Dot(T) * N; Nb = P0.Dot(B) * N; Tn = P0.Dot(N) * T; // = depth * T Bn = P0.Dot(N) * B; // = depth * B #if true M.r0.Set((Tn - Nt) / Rt, 0); M.r1.Set((Bn - Nb) / Rb, 0); M.r2.Set(0, 0, 1, 0); M.r3.Set(-N, depth); #else Dtn = (Nt - Tn) / Rt; Dbn = (Nb - Bn) / Rb; M.r0.Set(Dtn, -P0.Dot(Dtn)); M.r1.Set(Dbn, -P0.Dot(Dbn)); M.r2.Set(N / depth, 0.0f); // Will normalize Z to 1 if P is at same altitude as P0 M.r3.Set(N, -P0.Dot(N)); // Here, use "depth" #endif det = M.Determinant; determinants[Z - 1, COUNT_Y + Y, COUNT_X + X] = det; // Test matrix is working P.Set(P0, 1); Pt = M * P; // Pt /= Pt.w; // This will NaN because W=0 in this particular case P.Set(Rt * T, 1); Pt = M * P; Pt /= Pt.w; P.Set(-Rt * T, 1); Pt = M * P; Pt /= Pt.w; P.Set(Rb * B, 1); Pt = M * P; Pt /= Pt.w; P.Set(-Rb * B, 1); Pt = M * P; Pt /= Pt.w; P.Set(float3.Lerp(0.5f * Rt * T - 0.25f * Rb * B, P0, 0.666f), 1); Pt = M * P; Pt /= Pt.w; #endif } } } } }
private void timer1_Tick(object sender, EventArgs e) { if (!m_bHasRendered) { return; } m_bHasRendered = false; // Perform simulation int neighborsCount = m_neighborPositions.Length; bool hemi = checkBoxHemisphere.Checked; float solidAngle = (float)(2.0 * Math.PI / neighborsCount); // Hemisphere mode only covers 2PI float cosHalfAngle = 1.0f - solidAngle / (float)Math.PI; float halfAngle = (float)Math.Acos(cosHalfAngle); // Not used, just to check // Compute pressure forces float F = 0.01f * floatTrackbarControlForce.Value; float3[] forces = new float3[neighborsCount]; for (int i = 0; i < neighborsCount - 1; i++) { float3 D0 = m_neighborPositions[i]; for (int j = i + 1; j < neighborsCount; j++) { float3 D1 = m_neighborPositions[j]; float3 Dir = (D1 - D0).Normalized; float Dot = D0.Dot(D1) - 1.0f; // in [0,-2] float Force = F * (float)Math.Exp(Dot); forces[i] = forces[i] - Force * Dir; // Pushes 0 away from 1 forces[j] = forces[j] + Force * Dir; // Pushes 1 away from 0 } if (hemi) { // forces[i] += Math.Max( 0.0f, cosHalfAngle / Math.Max( 1e-1f, D0.z ) - 1.0f ) * float3.UnitZ; forces[i] += forces[i].Length * F * Math.Max(0.0f, 1.0f - D0.y / cosHalfAngle) * float3.UnitY; } } if (hemi) { forces[neighborsCount - 1] += forces[neighborsCount - 1].Length * F * Math.Max(0.0f, 1.0f - forces[neighborsCount - 1].y / cosHalfAngle) * float3.UnitY; } if (checkBoxForceOneSample.Checked) { // Force first sample up m_neighborPositions[0] = float3.UnitY; forces[0] = float3.Zero; } // Apply force for (int i = 0; i < neighborsCount; i++) { float3 newPosition = (m_neighborPositions[i] + forces[i]).Normalized; m_neighborPositions[i] = newPosition; m_SB_Neighbors.m[i].m_Position = newPosition; } // Update m_SB_Neighbors.Write(); if (checkBoxRenderCell.Checked) { buttonBuildCell_Click(this, EventArgs.Empty); Application.DoEvents(); } }
private void PerformExpectationMaximization(float3[] _Directions, FitLobe[] _Lobes) { int n = _Directions.Length; double invN = 1.0 / n; int k = _Lobes.Length; double[,] probabilities = new double[n, k]; // 1] Initialize lobes for (int h = 0; h < k; h++) { _Lobes[h].Direction = _Directions[(int)((h + 0.5f) * n / k)]; _Lobes[h].Concentration = 0.5; _Lobes[h].Alpha = 1.0 / k; } // 2] Iterate int iterationsCount = 0; while (++iterationsCount < 1000) { // 2.1] Compute Expectation (the E step of the algorithm) for (int i = 0; i < n; i++) { float3 dir = _Directions[i]; // 2.1.1) Compute weighted probability for each direction to belong to each lobe double weightedSumProbabilities = 0.0; for (int h = 0; h < k; h++) { FitLobe Lobe = _Lobes[h]; double kappa = Lobe.Concentration; double dot = dir.Dot(Lobe.Direction); double f = kappa / (2.0 * Math.PI * (Math.Exp(kappa) - Math.Exp(-kappa))) * Math.Exp(kappa * dot); f *= Lobe.Alpha; probabilities[i, h] = f; weightedSumProbabilities += f; } // 2.1.2) Normalize double normalizer = weightedSumProbabilities > 1e-12 ? 1.0 / weightedSumProbabilities : 0.0; for (int h = 0; h < k; h++) { probabilities[i, h] *= normalizer; } } // 2.2] Compute Maximization (the M step of the algorithm) double sqConvergenceRate = 0.0; for (int h = 0; h < k; h++) { FitLobe Lobe = _Lobes[h]; // Accumulate new alpha and average direction Lobe.Alpha = 0.0; double mu_x = 0.0; double mu_y = 0.0; double mu_z = 0.0; for (int i = 0; i < n; i++) { float3 dir = _Directions[i]; double p = probabilities[i, h]; double p_over_N = invN * p; Lobe.Alpha += p_over_N; mu_x += p_over_N * dir.x; mu_y += p_over_N * dir.y; mu_z += p_over_N * dir.z; } // Compute new direction double mu_length = Math.Sqrt(mu_x * mu_x + mu_y * mu_y + mu_z * mu_z); double r = Lobe.Alpha > 1e-12 ? mu_length / Lobe.Alpha : 0.0; // Normalize direction mu_length = mu_length > 1e-12 ? 1.0 / mu_length : 0.0; Lobe.Direction.x = (float)(mu_length * mu_x); Lobe.Direction.y = (float)(mu_length * mu_y); Lobe.Direction.z = (float)(mu_length * mu_z); // Compute new concentration double oldConcentration = Lobe.Concentration; double newConcentration = (3.0 * r - r * r * r) / (1.0 - r * r); Lobe.Concentration = newConcentration; sqConvergenceRate += (newConcentration - oldConcentration) * (newConcentration - oldConcentration); } sqConvergenceRate /= k * k; if (sqConvergenceRate < 1e-6) { break; } } }
// float DiskIrradiance(float3[] _tsQuadVertices) { // 1) Extract center position, tangent and bi-tangent axes float3 T = 0.5f * (_tsQuadVertices[1] - _tsQuadVertices[2]); float3 B = -0.5f * (_tsQuadVertices[3] - _tsQuadVertices[2]); float3 P0 = 0.5f * (_tsQuadVertices[0] + _tsQuadVertices[2]); // Half-way through the diagonal float sqRt = T.Dot(T); float sqRb = B.Dot(B); float3 N = T.Cross(B).Normalized; // @TODO: Optimize! Do we need to normalize anyway? // 2) Build frustum matrices // M transform P' = M * P into canonical frustum space float3x3 M; float invDepth = 1.0f / P0.Dot(N); M.r0 = (T - P0.Dot(T) * invDepth * N) / sqRt; M.r1 = (B - P0.Dot(B) * invDepth * N) / sqRb; M.r2 = N * invDepth; // M^-1 transforms P = M^-1 * P' back into original space float3x3 invM = new float3x3(); invM.r0.Set(T.x, B.x, P0.x); invM.r1.Set(T.y, B.y, P0.y); invM.r2.Set(T.z, B.z, P0.z); // Compute the determinant of M^-1 that will help us scale the resulting vector float det = invM.Determinant; det = Mathf.Sqrt(sqRt * sqRb) * P0.Dot(N); // 3) Compute the exact integral in the canonical space // We know the expression of the orthogonal vector at any point on the unit circle as: // // | cos(theta) // Tau(theta) = 1/sqrt(2) | sin(theta) // | 1 // // We move the orientation so we always compute 2 symetrical integrals on a half circle // then we only need to compute the X component integral as the Y components have opposite // sign and simply cancel each other // // The integral we need to compute is thus: // // X = Integral[-maxTheta,maxTheta]{ cos(theta) dtheta } // X = 2 * sin(maxTheta) // // The Z component is straightforward Z = Integral[-maxTheta,maxTheta]{ dtheta } = 2 maxTheta // float cosMaxTheta = -1.0f; // No clipping at the moment float maxTheta = Mathf.Acos(Mathf.Clamp(cosMaxTheta, -1, 1)); // @TODO: Optimize! Use fast acos or something... float3 F = new float3(2 * Mathf.Sqrt(1 - cosMaxTheta * cosMaxTheta), 0, 2 * maxTheta) / (Mathf.Sqrt(2) * Mathf.TWOPI); float3 F2 = F / -det; return(F2.Length); // // 4) Transform back into LTC space using M^-1 // float3 F2 = invM * F; // @TODO: Optimize => simply return length(F2) = determinant of M^-1 // // // 5) Estimate scalar irradiance //// return -1.0f / det; //// return Mathf.Abs(F2.z); // return F2.Length; }
/// <summary> /// Computes the 2 radii of the surface tangent to a vertex given a set of neighbor vertices /// </summary> /// <param name="_P"></param> /// <param name="_N"></param> /// <param name="_neighbors"></param> /// <returns></returns> float2 ComputeTangentCurvatureRadii(float3 _P, float3 _N, float3 _T, float3[] _neighbors, bool _debugGraph) { // Compute the square distance between the local neighbors and the double curvature surface of equation f(x,y) = Rt * x² + Rb * y² Func <double, double, float3[], double> SquareDistance = (double _Rt, double _Rb, float3[] _Pns) => { double result = 0.0; foreach (float3 Pn in _Pns) { double Fxy = _Rt * Pn.x * Pn.x + _Rb * Pn.y * Pn.y; double Dz = Fxy - Pn.z; result += Dz * Dz; // result += Dz; } return(result); }; // Transform neighbors into local space float3 B = _T.Cross(_N).Normalized; float3[] neighbors = new float3[_neighbors.Length]; for (int neighborIndex = 0; neighborIndex < _neighbors.Length; neighborIndex++) { float3 D = _neighbors[neighborIndex] - _P; neighbors[neighborIndex].Set(D.Dot(_T), D.Dot(B), D.Dot(_N)); } float2 rangeX = new float2(-100, 100), rangeY = new float2(-1000, 1000); float4 pipo = new float4(1, 0, 0, 1); if (_debugGraph) { graph.WritePixels((uint X, uint Y, ref float4 _color) => { float R0 = rangeX.x + X * (rangeX.y - rangeX.x) / graph.Width; float R1 = rangeY.y + Y * (rangeY.x - rangeY.y) / graph.Height; float sqDistance = (float)SquareDistance(R0, R1, _neighbors); float V = Math.Abs(sqDistance / 200.0f); _color.Set(V, V, V, 1); }); graph.DrawLine(new float4(0, 0, 1, 1), new float2(0, 0.5f * graph.Height), new float2(graph.Width, 0.5f * graph.Height)); graph.DrawLine(new float4(0, 0, 1, 1), new float2(0.5f * graph.Width, 0), new float2(0.5f * graph.Width, graph.Height)); } const float eps = 0.01f; const double tol = 1e-3; // Compute gradient descent double previousRt = -double.MaxValue; double previousRb = -double.MaxValue; double Rt = 0.0, Rb = 0.0; int iterationsCount = 0; double previousSqDistance = double.MaxValue; double bestSqDistance = double.MaxValue; double bestRt = 0.0; double bestRb = 0.0; double stepSize = 10.0; int bestIterationsCount = 0; while (Math.Abs(Rt) < 10000.0f && Math.Abs(Rb) < 10000.0f && iterationsCount < 1000) { // Compute gradient double sqDistance = SquareDistance(Rt, Rb, neighbors); // Central value if (Math.Abs(sqDistance - previousSqDistance) / sqDistance < tol) { break; // Relative error is low enough } double sqDistance_dT = SquareDistance(Rt + eps, Rb, neighbors); double sqDistance_dB = SquareDistance(Rt, Rb + eps, neighbors); // double sqDistance_dTdB = SquareDistance( Rt + eps, Rb + eps, neighbors ); double grad_dT = (sqDistance_dT - sqDistance) / eps; double grad_dB = (sqDistance_dB - sqDistance) / eps; // double grad_dTdB = (sqDistance_dTdB - sqDistance) / eps; // grad_dT = 0.5 * (grad_dT + grad_dTdB); // grad_dB = 0.5 * (grad_dB + grad_dTdB); // Compute intersection with secant Y=0 // double t_T = -sqDistance * (Math.Abs( grad_dT ) > 1e-6 ? 1.0 / grad_dT : (Math.Sign( grad_dT ) * 1e6)); // double t_B = -sqDistance * (Math.Abs( grad_dB ) > 1e-6 ? 1.0 / grad_dB : (Math.Sign( grad_dB ) * 1e6)); double t_T = -stepSize * eps * grad_dT; double t_B = -stepSize * eps * grad_dB; if (t_T * t_T + t_B * t_B < tol * tol) { break; } previousRt = Rt; previousRb = Rb; Rt += t_T; Rb += t_B; iterationsCount++; if (_debugGraph) { graph.DrawLine(pipo, new float2(((float)previousRt - rangeX.x) * graph.Width / (rangeX.y - rangeX.x), ((float)previousRb - rangeY.y) * graph.Height / (rangeY.x - rangeY.y)) , new float2(((float)Rt - rangeX.x) * graph.Width / (rangeX.y - rangeX.x), ((float)Rb - rangeY.y) * graph.Height / (rangeY.x - rangeY.y))); } // Keep best results previousSqDistance = sqDistance; if (sqDistance < bestSqDistance) { bestSqDistance = sqDistance; bestRt = previousRt; bestRb = previousRb; bestIterationsCount = iterationsCount; } } Rt = Math.Max(-10000.0, Math.Min(10000.0, Rt)); Rb = Math.Max(-10000.0, Math.Min(10000.0, Rb)); if (_debugGraph) { panelOutputGraph.m_bitmap = graph.AsBitmap; panelOutputGraph.Refresh(); labelResult.Text = "R = " + Rt.ToString("G4") + ", " + Rb.ToString("G4") + " (" + previousSqDistance.ToString("G4") + ") in " + iterationsCount + " iterations...\r\nBest = " + bestRt.ToString("G4") + ", " + bestRb.ToString("G4") + " (" + bestSqDistance.ToString("G4") + ") in " + bestIterationsCount + " iterations..."; } return(new float2((float)Rt, (float)Rb)); }
void UpdateGraph() { // float3 P = new float3( 1, 1, 1 ); // float3 N = P.Normalized; // float R = ComputeTangentSphereRadius( P, N, new float3[] { new float3( 1, 1, -1 ), new float3( -1, 1, 1 ), new float3( 1, -1, 1 ) } ); // // float R = ComputeTangentSphereRadius( P, N, new float3[] { new float3( 1, 1, 0 ), new float3( 0, 1, 1 ), new float3( 1, 0, 1 ) } ); // // // float3 P = new float3( 1, 1, 0 ); // // float3 N = P.Normalized; // // float R = ComputeTangentSphereRadius( P, N, new float3[] { new float3( -1, 1, 0 ), new float3( 1, -1, 0 ) } ); float3 V0 = float3.One; float3 N0 = V0.Normalized; float3 V1 = new float3(1, 1, -1); float3 V2 = new float3(-1, 1, 1); float3 V3 = new float3(1, -1, 1); float3 D0 = V1 - V0; float3 D1 = V2 - V0; float3 D2 = V3 - V0; float L = D0.Length; float L_ = (V1 - D0.Dot(N0) * N0 - V0).Length; float3 P = float3.Zero; float3 N = float3.UnitZ; float A = floatTrackbarControlA.Value; float B = floatTrackbarControlB.Value; float C = floatTrackbarControlC.Value; float a = (float)(2 * Math.Sqrt(2.0 / 3)); float b = a * (float)Math.Sqrt(3) / 2.0f; // float L1 = (new float3( 0, a, A ) - P).Length; // float L2 = (new float3( -b, -0.5f * a, A ) - P).Length; // float L3 = (new float3( b, -0.5f * a, A ) - P).Length; // float R = ComputeTangentSphereRadius( P, N, new float3[] { new float3( 0, a, A ), new float3( -b, -0.5f * a, B ), new float3( b, -0.5f * a, C ) }, true ); float D = floatTrackbarControlD.Value; // float2 curvature = ComputeTangentCurvatureRadii( P, N, float3.UnitX, new float3[] { new float3( -1, 0, A ), new float3( +1, 0, B ), new float3( 0, -1, C ), new float3( 0, +1, D ) }, true ); // Torus inner vertex P = new float3(0.5f, 0.0f, 0.0f); N = new float3(-1.0f, 0.0f, 0.0f); float3 T = new float3(0.0f, 1.0f, 0.0f); float3[] neighbors = new float3[] { new float3(0.566346049f, -0.184016943f, 0.2938926f), new float3(0.595491469f, 0.0f, 0.2938926f), new float3(0.566346049f, 0.184016988f, 0.2938926f), new float3(0.47552827f, -0.154508471f, 0.0f), new float3(0.47552827f, 0.1545085f, 0.0f), new float3(0.5663461f, -0.184016973f, -0.293892682f), new float3(0.5954915f, 0.0f, -0.293892682f), new float3(0.5663461f, 0.184017f, -0.293892682f) }; float2 curvature2 = ComputeTangentCurvatureRadii(P, N, T, neighbors, true); P = new float3(1.5f, 0.0f, 0.0f); N = new float3(1.0f, 0.0f, 0.0f); T = new float3(0.0f, 1.0f, 0.0f); neighbors = new float3[] { new float3(1.335767f, -0.4340169f, -0.293892652f), new float3(1.40450847f, 0.0f, -0.293892652f), new float3(1.335767f, 0.434017f, -0.293892652f), new float3(1.42658484f, -0.4635254f, 0.0f), new float3(1.42658484f, 0.4635255f, 0.0f), new float3(1.335767f, -0.4340169f, 0.293892622f), new float3(1.40450847f, 0.0f, 0.293892622f), new float3(1.335767f, 0.434017f, 0.293892622f), }; float2 curvature3 = ComputeTangentCurvatureRadii(P, N, T, neighbors, true); }
float3 GatherIrradiance(float2 _csDirection, float4x4 _localCamera2World, float3 _csNormal, float _stepSize_meters, uint _stepsCount, float _Z0, float3 _centralRadiance, out float3 _csBentNormal, out float2 _coneAngles, out float _AO, ref float4 _DEBUG) { // Pre-compute factors for the integrals float2 integralFactors_Front = ComputeIntegralFactors(_csDirection, _csNormal); float2 integralFactors_Back = ComputeIntegralFactors(-_csDirection, _csNormal); // Compute initial cos(angle) for front & back horizons // We do that by projecting the screen-space direction ssDirection onto the tangent plane given by the normal // then the cosine of the angle from the Z axis is simply given by the Pythagorean theorem: // P // N\ |Z -*- // \ | --- ^ // \| --- | // --- *..........>+ ssDirection // --- // float hitDistance_Front = -_csDirection.Dot(_csNormal.xy) / _csNormal.z; float maxCosTheta_Front = hitDistance_Front / Mathf.Sqrt(hitDistance_Front * hitDistance_Front + 1.0f); float maxCosTheta_Back = -maxCosTheta_Front; // Back cosine is simply the mirror value // Gather irradiance from front & back directions while updating the horizon angles at the same time float3 sumRadiance = float3.Zero; float3 previousRadiance_Front = _centralRadiance; float3 previousRadianceBack = _centralRadiance; //* // float2 radius = float2.Zero; float2 csStep = _stepSize_meters * _csDirection; float2 csPosition_Front = float2.Zero; float2 csPosition_Back = float2.Zero; for (uint stepIndex = 0; stepIndex < _stepsCount; stepIndex++) { // radius += _stepSize_meters; csPosition_Front += csStep; csPosition_Back -= csStep; // float2 mipLevel = ComputeMipLevel( radius, _radialStepSizes ); float2 mipLevel = float2.Zero; sumRadiance += SampleIrradiance(csPosition_Front, _localCamera2World, _Z0, mipLevel, integralFactors_Front, ref previousRadiance_Front, ref maxCosTheta_Front); sumRadiance += SampleIrradiance(csPosition_Back, _localCamera2World, _Z0, mipLevel, integralFactors_Back, ref previousRadianceBack, ref maxCosTheta_Back); } //*/ // Accumulate bent normal direction by rebuilding and averaging the front & back horizon vectors float2 ssNormal = new float2(_csNormal.xy.Dot(_csDirection), _csNormal.z); #if USE_NUMERICAL_INTEGRATION // Half brute force where we perform the integration numerically as a sum... // const uint STEPS = 256; float thetaFront = Mathf.Acos(maxCosTheta_Front); float thetaBack = -Mathf.Acos(maxCosTheta_Back); float2 ssBentNormal = float2.Zero; for (uint i = 0; i < STEPS; i++) { float theta = Mathf.Lerp(thetaBack, thetaFront, (i + 0.5f) / STEPS); float sinTheta = Mathf.Sin(theta), cosTheta = Mathf.Cos(theta); float2 ssOmega = new float2(sinTheta, cosTheta); float cosAlpha = Mathf.Saturate(ssOmega.Dot(ssNormal)); cosAlpha = 1.0f; float weight = cosAlpha * Mathf.Abs(sinTheta); // cos(alpha) * sin(theta).dTheta (be very careful to take abs(sin(theta)) because our theta crosses the pole and becomes negative here!) ssBentNormal += weight * ssOmega; } float dTheta = (thetaFront - thetaBack) / STEPS; ssBentNormal *= dTheta; _csBentNormal = new float3(ssBentNormal.x * _csDirection, ssBentNormal.y); #else // Analytical solution // Accounts for dot product with normal // float cosTheta0 = maxCosTheta_Front; // float cosTheta1 = maxCosTheta_Back; // float sinTheta0 = Mathf.Sqrt( 1.0f - cosTheta0*cosTheta0 ); // float sinTheta1 = Mathf.Sqrt( 1.0f - cosTheta1*cosTheta1 ); // // float cosTheta0_3 = cosTheta0*cosTheta0*cosTheta0; // float cosTheta1_3 = cosTheta1*cosTheta1*cosTheta1; // float sinTheta0_3 = sinTheta0*sinTheta0*sinTheta0; // float sinTheta1_3 = sinTheta1*sinTheta1*sinTheta1; // // float averageX = ssNormal.x * (cosTheta0_3 + cosTheta1_3 - 3.0f * (cosTheta0 + cosTheta1) + 4.0f) // + ssNormal.y * (sinTheta0_3 - sinTheta1_3); // // float averageY = ssNormal.x * (sinTheta0_3 - sinTheta1_3) // + ssNormal.y * (2.0f - cosTheta0_3 - cosTheta1_3); // // Raw integration, without dot product with normal float theta0 = -Mathf.Acos(maxCosTheta_Back); float theta1 = Mathf.Acos(maxCosTheta_Front); float averageX = theta1 + theta0 - Mathf.Sin(theta0) * Mathf.Cos(theta0) - Mathf.Sin(theta1) * Mathf.Cos(theta1); float averageY = 2.0f - Mathf.Cos(theta0) * Mathf.Cos(theta0) - Mathf.Cos(theta1) * Mathf.Cos(theta1); _csBentNormal = new float3(averageX * _csDirection, averageY); #endif // DON'T NORMALIZE RESULT!! // _csBentNormal = _csBentNormal.Normalized; // Compute cone angles float3 csNormalizedBentNormal = _csBentNormal.Normalized; float3 csHorizon_Front = new float3(Mathf.Sqrt(1.0f - maxCosTheta_Front * maxCosTheta_Front) * _csDirection, maxCosTheta_Front); float3 csHorizon_Back = new float3(-Mathf.Sqrt(1.0f - maxCosTheta_Back * maxCosTheta_Back) * _csDirection, maxCosTheta_Back); #if USE_FAST_ACOS _coneAngles.x = FastPosAcos(saturate(dot(csNormalizedBentNormal, csHorizon_Front))); _coneAngles.y = FastPosAcos(saturate(dot(csNormalizedBentNormal, csHorizon_Back))); #else _coneAngles.x = Mathf.Acos(Mathf.Saturate(csNormalizedBentNormal.Dot(csHorizon_Front))); _coneAngles.y = Mathf.Acos(Mathf.Saturate(csNormalizedBentNormal.Dot(csHorizon_Back))); #endif // Compute AO using equation 11 of the paper _AO = 2.0f - maxCosTheta_Back - maxCosTheta_Front; // _AO = 0.0f; // // float theta0 = -Mathf.Acos( maxCosTheta_Back ); // // float theta1 = Mathf.Acos( maxCosTheta_Front ); // for ( uint i=0; i < 256; i++ ) { // float theta = Mathf.Lerp( theta0, theta1, (i+0.5f) / 256 ); // _AO += Math.Abs( Mathf.Sin( theta ) ); // } // _AO *= (theta1 - theta0) / 256.0f; return(sumRadiance); }
// From Walter 2007 eq. 40 // Expects _incoming pointing AWAY from the surface // eta = IOR_above / IOR_below // public static float3 Refract( float3 _incoming, float3 _normal, float _eta ) { float c = _incoming.Dot( _normal ); float k = (float) (_eta * c - Math.Sign(c) * Math.Sqrt( 1.0 + _eta * (c*c - 1.0) )); float3 R = k * _normal - _eta * _incoming; return R.Normalized; }
void NumericalIntegration() { // Generate a bunch of rays with equal probability on the hemisphere const int THETA_SAMPLES = 100; const int SAMPLES_COUNT = 4 * THETA_SAMPLES * THETA_SAMPLES; const double dPhi = 2.0 * Math.PI / (4 * THETA_SAMPLES); float3[] directions = new float3[SAMPLES_COUNT]; for (int Y = 0; Y < THETA_SAMPLES; Y++) { for (int X = 0; X < 4 * THETA_SAMPLES; X++) { double phi = dPhi * (X + SimpleRNG.GetUniform()); double theta = 2.0 * Math.Acos(Math.Sqrt(1.0 - 0.5 * (Y + SimpleRNG.GetUniform()) / THETA_SAMPLES)); // Uniform sampling on theta directions[4 * THETA_SAMPLES * Y + X].Set((float)(Math.Sin(theta) * Math.Cos(phi)), (float)(Math.Sin(theta) * Math.Sin(phi)), (float)Math.Cos(theta)); } } // Compute numerical integration for various sets of angles const int TABLE_SIZE = 100; float3 coneDirection = float3.Zero; float3[,] integratedSHCoeffs = new float3[TABLE_SIZE, TABLE_SIZE]; double avgDiffA0 = 0.0; double avgDiffA1 = 0.0; double avgDiffA2 = 0.0; for (int thetaIndex = 0; thetaIndex < TABLE_SIZE; thetaIndex++) { // float cosTheta = 1.0f - (float) thetaIndex / TABLE_SIZE; float cosTheta = (float)Math.Cos(0.5 * Math.PI * thetaIndex / TABLE_SIZE); coneDirection.x = (float)Math.Sqrt(1.0f - cosTheta * cosTheta); coneDirection.z = cosTheta; for (int AOIndex = 0; AOIndex < TABLE_SIZE; AOIndex++) { // float AO = 1.0f - (float) AOIndex / TABLE_SIZE; // float coneHalfAngle = 0.5f * (float) Math.PI * AO; // Cone half angle varies in [0,PI/2] // float cosConeHalfAngle = (float) Math.Cos( coneHalfAngle ); float cosConeHalfAngle = (float)AOIndex / TABLE_SIZE; double A0 = 0.0; double A1 = 0.0; double A2 = 0.0; for (int sampleIndex = 0; sampleIndex < SAMPLES_COUNT; sampleIndex++) { float3 direction = directions[sampleIndex]; if (direction.Dot(coneDirection) < cosConeHalfAngle) { continue; // Sample is outside cone } float u = direction.z; // cos(theta_sample) float u2 = u * u; float u3 = u * u2; A0 += u; //A0 += 1.0; A1 += u2; A2 += 0.5 * (3 * u3 - u); } A0 *= 2.0 * Math.PI / SAMPLES_COUNT; A1 *= 2.0 * Math.PI / SAMPLES_COUNT; A2 *= 2.0 * Math.PI / SAMPLES_COUNT; A0 *= Math.Sqrt(1.0 / (4.0 * Math.PI)); A1 *= Math.Sqrt(3.0 / (4.0 * Math.PI)); A2 *= Math.Sqrt(5.0 / (4.0 * Math.PI)); // float3 verify = EstimateLambertReflectanceFactors( cosConeHalfAngle, 0.5f * (float) Math.PI * thetaIndex / TABLE_SIZE ); // avgDiffA0 += Math.Abs( A0 - verify.x ); // avgDiffA1 += Math.Abs( A1 - verify.y ); // avgDiffA2 += Math.Abs( A2 - verify.z ); integratedSHCoeffs[thetaIndex, AOIndex].Set((float)A0, (float)A1, (float)A2); } } avgDiffA0 /= TABLE_SIZE * TABLE_SIZE; avgDiffA1 /= TABLE_SIZE * TABLE_SIZE; avgDiffA2 /= TABLE_SIZE * TABLE_SIZE; using (System.IO.FileStream S = new System.IO.FileInfo(@"ConeTable_cosAO.float3").Create()) using (System.IO.BinaryWriter W = new System.IO.BinaryWriter(S)) { for (int thetaIndex = 0; thetaIndex < TABLE_SIZE; thetaIndex++) { for (int AOIndex = 0; AOIndex < TABLE_SIZE; AOIndex++) { W.Write(integratedSHCoeffs[thetaIndex, AOIndex].x); W.Write(integratedSHCoeffs[thetaIndex, AOIndex].y); W.Write(integratedSHCoeffs[thetaIndex, AOIndex].z); } } } }
public double Eval(double[] _newParameters) { double lobeTheta = _newParameters[0]; double lobeRoughness = _newParameters[1]; double lobeGlobalScale = _newParameters[2]; double lobeFlatten = _newParameters[3]; double maskingImportance = _newParameters[4]; // Flattening is not linear when using the anisotropic lobe model! if (m_lobeType == LOBE_TYPE.MODIFIED_PHONG_ANISOTROPIC) { lobeFlatten = Math.Pow(2.0, 4.0 * (lobeFlatten - 1.0)); // in [2e-4, 2e4], log space } double invLobeFlatten = 1.0 / lobeFlatten; // Compute constant masking term due to incoming direction double maskingIncoming = Masking(m_direction.z, lobeRoughness); // Masking( incoming ) // Compute lobe's reflection vector and tangent space using new parameters double cosTheta = Math.Cos(lobeTheta); double sinTheta = Math.Sin(lobeTheta); float3 lobe_normal = new float3((float)(sinTheta * m_incomingDirection_CosPhi), (float)(sinTheta * m_incomingDirection_SinPhi), (float)cosTheta); float3 lobe_tangent = new float3((float)-m_incomingDirection_SinPhi, (float)m_incomingDirection_CosPhi, 0.0f); // Always lying in the X^Y plane float3 lobe_biTangent = lobe_normal.Cross(lobe_tangent); // Compute sum double phi, theta, cosPhi, sinPhi; double outgoingIntensity_Simulated, length; double outgoingIntensity_Analytical, lobeIntensity; double difference; float3 wsOutgoingDirection = float3.Zero; float3 wsOutgoingDirection2 = float3.Zero; float3 lsOutgoingDirection = float3.Zero; double maskingOutGoing = 0.0; double maskingShadowing; double sum = 0.0; double sum_Simulated = 0.0; double sum_Analytical = 0.0; double sqSum_Simulated = 0.0; double sqSum_Analytical = 0.0; for (int Y = 0; Y < H; Y++) { // Formerly used wrong stuff! // // Y = theta bin index = 2.0 * LOBES_COUNT_THETA * pow2( sin( 0.5 * theta ) ) // // We need theta: // theta = 2.0 * Math.Asin( Math.Sqrt( 0.5 * Y / H ) ); // Y = theta bin index = LOBES_COUNT_THETA * (1 - cos( theta ) ) // // We need theta: theta = Math.Acos(1.0 - (float)Y / H); cosTheta = Math.Cos(theta); sinTheta = Math.Sin(theta); for (int X = 0; X < W; X++) { // X = phi bin index = LOBES_COUNT_PHI * X / (2PI) // We need phi: phi = 2.0 * Math.PI * X / W; cosPhi = Math.Cos(phi); sinPhi = Math.Sin(phi); // Build simulated microfacet reflection direction in macro-surface space outgoingIntensity_Simulated = m_histogramData[X, Y]; wsOutgoingDirection.Set((float)(cosPhi * sinTheta), (float)(sinPhi * sinTheta), (float)cosTheta); // Compute maksing term due to outgoing direction maskingOutGoing = Masking(wsOutgoingDirection.z, lobeRoughness); // Masking( outgoing ) // Compute projection of world space direction onto reflected direction float Vx = wsOutgoingDirection.Dot(lobe_tangent); float Vy = wsOutgoingDirection.Dot(lobe_biTangent); float Vz = wsOutgoingDirection.Dot(lobe_normal); //Vz = Math.Min( 0.99f, Vz ); float cosTheta_M = Math.Max(1e-6f, Vz); // Compute the lobe intensity in local space lobeIntensity = NDF(cosTheta_M, lobeRoughness); maskingShadowing = 1.0 + maskingImportance * (maskingIncoming * maskingOutGoing - 1.0); // = 1 when importance = 0, = masking when importance = 1 lobeIntensity *= maskingShadowing; // * Masking terms lobeIntensity *= lobeGlobalScale; // Apply additional lobe scaling/flattening length = m_flatteningEval(Vx, Vy, Vz, lobeFlatten, invLobeFlatten); outgoingIntensity_Analytical = lobeIntensity * length; // Lobe intensity was estimated in lobe space, account for scaling when converting back in world space // Sum the difference between simulated intensity and lobe intensity outgoingIntensity_Analytical *= m_oversizeFactor; // Apply tolerance factor so we're always a bit smaller than the simulated lobe if (m_fitUsingCenterOfMass) { double difference0 = outgoingIntensity_Simulated - outgoingIntensity_Analytical; float3 wsLobePosition_Simulated = (float)outgoingIntensity_Simulated * wsOutgoingDirection; float3 wsLobePosition_Analytical = (float)outgoingIntensity_Analytical * wsOutgoingDirection; // Subtract center of mass wsLobePosition_Simulated -= m_centerOfMass; wsLobePosition_Analytical -= m_centerOfMass; // Compute new intensities, relative to center of mass outgoingIntensity_Simulated = wsLobePosition_Simulated.Length; outgoingIntensity_Analytical = wsLobePosition_Analytical.Length; double difference1 = outgoingIntensity_Simulated - outgoingIntensity_Analytical; difference = 0.5 * difference0 + 0.5 * difference1; // difference *= (wsLobePosition_Simulated - wsLobePosition_Analytical).Length; // difference += (wsLobePosition_Simulated - wsLobePosition_Analytical).Length; // We also add the distance between lobe positions so it goes to the best of the 2 minima! } else { difference = outgoingIntensity_Simulated - outgoingIntensity_Analytical; // difference = outgoingIntensity_Simulated / Math.Max( 1e-6, outgoingIntensity_Analytical ) - 1.0; // difference = outgoingIntensity_Analytical / Math.Max( 1e-6, outgoingIntensity_Simulated ) - 1.0; } sum += difference * difference; sum_Simulated += outgoingIntensity_Simulated; sum_Analytical += outgoingIntensity_Analytical; sqSum_Simulated += outgoingIntensity_Simulated * outgoingIntensity_Simulated; sqSum_Analytical += outgoingIntensity_Analytical * outgoingIntensity_Analytical; } } sum /= W * H; // Not very useful since BFGS won't care but I'm doing it anyway to have some sort of normalized sum, better for us humans return(sum); }
public void Compute(uint _X, uint _Y) { float2 __Position = new float2(0.5f + _X, 0.5f + _Y); float2 UV = __Position / m_resolution; uint pixelPositionX = (uint)Mathf.Floor(__Position.x); uint pixelPositionY = (uint)Mathf.Floor(__Position.y); float noise = 0.0f; //_tex_blueNoise[pixelPosition & 0x3F]; //PerformIntegrationTest(); // Setup camera ray float3 csView = BuildCameraRay(UV); float Z2Distance = csView.Length; csView /= Z2Distance; float3 wsView = (new float4(csView, 0.0f) * m_camera2World).xyz; // Read back depth, normal & central radiance value from last frame float Z = FetchDepth(__Position, 0.0f); Z -= 1e-2f; // Prevent acnea by offseting the central depth closer // float distance = Z * Z2Distance; float3 wsNormal = m_arrayNormal[0][pixelPositionX, pixelPositionY].xyz.Normalized; // Read back last frame's radiance value that we always can use as a default for neighbor areas float3 centralRadiance = m_arrayIrradiance[0][pixelPositionX, pixelPositionY].xyz; // Compute local camera-space float3 wsPos = m_camera2World[3].xyz + Z * Z2Distance * wsView; float3 wsRight = wsView.Cross(m_camera2World[1].xyz).Normalized; float3 wsUp = wsRight.Cross(wsView); float3 wsAt = -wsView; float4x4 localCamera2World = new float4x4(new float4(wsRight, 0), new float4(wsUp, 0), new float4(wsAt, 0), new float4(wsPos, 1)); // Compute local camera-space normal float3 N = new float3(wsNormal.Dot(wsRight), wsNormal.Dot(wsUp), wsNormal.Dot(wsAt)); N.z = Math.Max(1e-4f, N.z); // Make sure it's never 0! // float3 T, B; // BuildOrthonormalBasis( N, T, B ); // Compute screen radius of gather sphere float screenSize_m = 2.0f * Z * TAN_HALF_FOV; // Vertical size of the screen in meters when extended to distance Z float sphereRadius_pixels = m_resolution.y * m_gatherSphereMaxRadius_m / screenSize_m; sphereRadius_pixels = Mathf.Min(GATHER_SPHERE_MAX_RADIUS_P, sphereRadius_pixels); // Prevent it to grow larger than our fixed limit float radiusStepSize_pixels = Mathf.Max(1.0f, sphereRadius_pixels / MAX_SAMPLES); // This gives us our radial step size in pixels uint samplesCount = Mathf.Clamp((uint)Mathf.Ceiling(sphereRadius_pixels / radiusStepSize_pixels), 1, MAX_SAMPLES); // Reduce samples count if possible float radiusStepSize_meters = sphereRadius_pixels * screenSize_m / (samplesCount * m_resolution.y); // This gives us our radial step size in meters // Start gathering radiance and bent normal by subdividing the screen-space disk around our pixel into Z slices float4 GATHER_DEBUG = float4.Zero; float3 sumIrradiance = float3.Zero; float3 csAverageBentNormal = float3.Zero; // float averageConeAngle = 0.0f; // float varianceConeAngle = 0.0f; float sumAO = 0.0f; for (uint angleIndex = 0; angleIndex < MAX_ANGLES; angleIndex++) { float phi = (angleIndex + noise) * Mathf.PI / MAX_ANGLES; //phi = 0.0f; float2 csDirection; csDirection.x = Mathf.Cos(phi); csDirection.y = Mathf.Sin(phi); // Gather irradiance and average cone direction for that slice float3 csBentNormal; float2 coneAngles; float AO; sumIrradiance += GatherIrradiance(csDirection, localCamera2World, N, radiusStepSize_meters, samplesCount, Z, centralRadiance, out csBentNormal, out coneAngles, out AO, ref GATHER_DEBUG); // if ( AO < -0.01f || AO > 1.01f ) // throw new Exception( "MERDE!" ); csAverageBentNormal += csBentNormal; sumAO += AO; // // We're using running variance computation from https://www.johndcook.com/blog/standard_deviation/ // // Avg(N) = Avg(N-1) + [V(N) - Avg(N-1)] / N // // S(N) = S(N-1) + [V(N) - Avg(N-1)] * [V(N) - Avg(N)] // // And variance = S(finalN) / (finalN-1) // // // float previousAverageConeAngle = averageConeAngle; // averageConeAngle += (coneAngles.x - averageConeAngle) / (2*angleIndex+1); // varianceConeAngle += (coneAngles.x - previousAverageConeAngle) * (coneAngles.x - averageConeAngle); // // previousAverageConeAngle = averageConeAngle; // averageConeAngle += (coneAngles.y - averageConeAngle) / (2*angleIndex+2); // varianceConeAngle += (coneAngles.y - previousAverageConeAngle) * (coneAngles.y - averageConeAngle); } // Finalize bent cone & irradiance csAverageBentNormal = csAverageBentNormal.Normalized; //csAverageBentNormal *= Mathf.PI / MAX_ANGLES; sumIrradiance *= Mathf.PI / MAX_ANGLES; // varianceConeAngle /= 2.0f*MAX_ANGLES - 1.0f; // float stdDeviation = Mathf.Sqrt( varianceConeAngle ); sumAO /= 2.0f * MAX_ANGLES; //if ( sumAO < 0.0f || sumAO > 1.0f ) // throw new Exception( "MERDE!" ); float averageConeAngle = Mathf.Acos(1.0f - sumAO); float varianceConeAngle = 0.0f; // Unfortunately, we don't have a proper value for the variance anymore... :'( float stdDeviation = Mathf.Sqrt(varianceConeAngle); sumIrradiance.Max(float3.Zero); ////////////////////////////////////////////////////////////////////////// // Finalize results m_sumIrradiance = new float4(sumIrradiance, 0); m_bentCone = new float4(Mathf.Max(0.01f, Mathf.Cos(averageConeAngle)) * csAverageBentNormal, 1.0f - stdDeviation / (0.5f * Mathf.PI)); float3 DEBUG_VALUE = new float3(1, 0, 1); DEBUG_VALUE = csAverageBentNormal; DEBUG_VALUE = csAverageBentNormal.x * wsRight - csAverageBentNormal.y * wsUp - csAverageBentNormal.z * wsAt; // World-space normal //DEBUG_VALUE = cos( averageConeAngle ); //DEBUG_VALUE = dot( ssAverageBentNormal, N ); //DEBUG_VALUE = 0.01 * Z; //DEBUG_VALUE = sphereRadius_pixels / GATHER_SPHERE_MAX_RADIUS_P; //DEBUG_VALUE = 0.1 * (radiusStepSize_pixels-1); //DEBUG_VALUE = 0.5 * float(samplesCount) / MAX_SAMPLES; //DEBUG_VALUE = varianceConeAngle; //DEBUG_VALUE = stdDeviation; //DEBUG_VALUE = float3( GATHER_DEBUG.xy, 0 ); //DEBUG_VALUE = float3( GATHER_DEBUG.zw, 0 ); DEBUG_VALUE = GATHER_DEBUG.xyz; //DEBUG_VALUE = N; //m_bentCone = float4( DEBUG_VALUE, 1 ); ////////////////////////////////////////////////////////////////////////// // Finalize bent code debug info m_wsConePosition = wsPos; m_wsConeDirection = csAverageBentNormal.x * wsRight + csAverageBentNormal.y * wsUp + csAverageBentNormal.z * wsAt; //m_wsConeDirection = wsNormal; m_averageConeAngle = averageConeAngle; m_stdDeviation = stdDeviation; }