Analytical normals 3D code analysis

Analytical normals 3D code analysis

Author: iq, website: https://www.shadertoy.com/view/XttSz2

label: 3d, noise, normals, analytical, numerical

A total of one part: Image

Camera settings

This is more common, that is, simple camera rotation and matrix acquisition

// camera anim
float an = 0.1*iTime;
vec3 ro = 3.0*vec3( cos(an), 0.8, sin(an) );
vec3 ta = vec3( 0.0 );
// camera matrix	
vec3  cw = normalize( ta-ro );
vec3  cu = normalize( cross(cw,vec3(0.0,1.0,0.0)) );
vec3  cv = normalize( cross(cu,cw) );
vec3  rd = normalize( p.x*cu + p.y*cv + 1.7*cw );

Intersection test function

Then the intersection test is carried out

vec4 interesect( in vec3 ro, in vec3 rd )
{
	vec4 res = vec4(-1.0);

    // Square intersection test   
    vec2 dis = iBox( ro, rd, vec3(1.5) ) ;
    if( dis.y<0.0 ) return res;

    // raymarch
    float tmax = dis.y;
    float t = dis.x;
	for( int i=0; i<128; i++ )
	{
        vec3 pos = ro + t*rd;
		vec4 hnor = map( pos );
        res = vec4(t,hnor.yzw);
        
		if( hnor.x<0.001 ) break;
		t += hnor.x;
        if( t>tmax ) break;
	}

	if( t>tmax ) res = vec4(-1.0);
	return res;
}

The first is judgment Does the ray intersect the square , if they intersect, return the two intersections. For details about intersection test functions, please refer to IQ blog. Then RayMarching is performed within the range of [tmin,tmax] generated by the two intersections. The core is the Map function, as shown below:

vec4 map( in vec3 p )
{
	vec4 d1 = fbmd( p );
    d1.x -= 0.37;
	d1.x *= 0.7;
    d1.yzw = normalize(d1.yzw);
    // clip to box
    vec4 d2 = sdBox( p, vec3(1.5) );
    return (d1.x>d2.x) ? d1 : d2;
}

Specific analysis: for d1, it is through Fractional Brownian function Obtain random points (where x component is t and yzw is normal). The difference between this and the FBM function known before is that the normal vector of random points is obtained. Of course, we can also directly use the conventional calNormal function. For the result, interval remapping is carried out. Why do you need this remapping (one addition, one subtraction, one multiplication)? Its core is addition and subtraction, and multiplication doesn't matter (the effect is the same). Imagine that if we don't subtract a value, the return value of FBM is always greater than 0. No matter how we move forward (on FMB, the performance is moving on the surface), it won't end. In the effect performance, if we delete this line, all rays will be cast to infinity, Increasing the subtraction value, the closer the geometry is to the complete square.

Then, when judging the distance from the square, the two results are compared to return the result with larger value and normal vector. Return to the intersect function, followed by the conventional ray stepping operation. Finally, return the main function.

Rendering process

First calculate AO. Use spherical Fibonacci sampling (actually hemisphere) to obtain uniform sampling. Sample 32 times, map each time, accumulate the step distance of the returned result, and then divide by 32. There is a problem here - multiply 3 and 5 respectively during Clamp. Analysis here: because our judgment on the sampling point is its distance from the geometry, not the depth map of the light direction, even for the points on the surface of the cube, their return value will not be very large, so we need to multiply it by multiple.

float calcAO( in vec3 pos, in vec3 nor )
{
	float ao = 0.0;
    for( int i=0; i<32; i++ )
    {
        //Spherical Fibonacci uniform sampling
        vec3 ap = forwardSF( float(i), 32.0 );
        //Random value
        float h = hash(float(i));
        //Reverse normal direction hemisphere reverse direction
		ap *= sign( dot(ap,nor) ) * h*0.25;
        ao += clamp( map( pos + nor*0.001 + ap ).x*3.0, 0.0, 1.0 );
    }
	ao /= 32.0;
	
    return clamp( ao*5.0, 0.0, 1.0 );
}

Then calculate Fre, Fro.

float fre = clamp( 1.0+dot(rd,nor), 0.0, 1.0 );
float fro = clamp( dot(nor,-rd), 0.0, 1.0 );

Among them, Fre is the approximation of the Fresnel effect, which is roughly shown in the figure: in the figure, the more flat, the shorter the purple segment, the greater the value of 1 minus the purple segment, the stronger the Fresnel effect. Fro is the opposite, or close to the general calculation.

Then formally calculate the color

col = mix( vec3(0.05,0.2,0.3), vec3(1.0,0.95,0.85), 0.5+0.5*nor.y );
//col = 0.5+0.5*nor;
col += 10.0*pow(fro,12.0)*(0.04+0.96*pow(fre,5.0));
col *= pow(vec3(occ),vec3(1.0,1.1,1.1) );

The first is the color of the hook, which will be related to the color of the line of people. The second line, use the calculated fre, fro. I don't know the specific meaning. I'll have a chance to understand it in the future. The third line, add AO.

Finally, after switching to gamma space, it ends.

*Tool iBox function

vec2 iBox( in vec3 ro, in vec3 rd, in vec3 rad ) 
{
    vec3 m = 1.0/rd;
    vec3 n = m*ro;
    vec3 k = abs(m)*rad;
    vec3 t1 = -n - k;
    vec3 t2 = -n + k;
	float tN = max( max( t1.x, t1.y ), t1.z );
	float tF = min( min( t2.x, t2.y ), t2.z );
	if( tN > tF || tF < 0.0) return vec2(-1.0);
	return vec2( tN, tF );
}

*Tool FBMD function

vec4 fbmd( in vec3 x )
{
    const float scale  = 1.5;

    float a = 0.0;
    float b = 0.5;
	float f = 1.0;
    vec3  d = vec3(0.0);
    for( int i=0; i<8; i++ )
    {
        vec4 n = noised(f*x*scale);
        a += b*n.x;           // accumulate values		
        d += b*n.yzw*f*scale; // accumulate derivatives
        b *= 0.5;             // amplitude decrease
        f *= 1.8;             // frequency increase
    }
	return vec4( a, d );
}

*Tool SdBox function

vec4 sdBox( vec3 p, vec3 b ) // distance and normal
{
    vec3 d = abs(p) - b;
    float x = min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
    vec3  n = step(d.yzx,d.xyz)*step(d.zxy,d.xyz)*sign(p);
    return vec4( x, n );
}

*Tool ValueNoise and its gradient

float hash( float n ) { return fract(sin(n)*753.5453123); }
vec4 noised( in vec3 x )
{
    vec3 p = floor(x);
    vec3 w = fract(x);
	vec3 u = w*w*(3.0-2.0*w);
    vec3 du = 6.0*w*(1.0-w);
    
    float n = p.x + p.y*157.0 + 113.0*p.z;
    
    float a = hash(n+  0.0);
    float b = hash(n+  1.0);
    float c = hash(n+157.0);
    float d = hash(n+158.0);
    float e = hash(n+113.0);
	float f = hash(n+114.0);
    float g = hash(n+270.0);
    float h = hash(n+271.0);
	
    float k0 =   a;
    float k1 =   b - a;
    float k2 =   c - a;
    float k3 =   e - a;
    float k4 =   a - b - c + d;
    float k5 =   a - c - e + g;
    float k6 =   a - b - e + f;
    float k7 = - a + b + c - d + e - f - g + h;

    return vec4( k0 + k1*u.x + k2*u.y + k3*u.z + k4*u.x*u.y + k5*u.y*u.z + k6*u.z*u.x + k7*u.x*u.y*u.z, 
                 du * (vec3(k1,k2,k3) + u.yzx*vec3(k4,k5,k6) + u.zxy*vec3(k6,k4,k5) + k7*u.yzx*u.zxy ));
}
return vec4( k0 + k1*u.x + k2*u.y + k3*u.z + k4*u.x*u.y + k5*u.y*u.z + k6*u.z*u.x + k7*u.x*u.y*u.z, 
             du * (vec3(k1,k2,k3) + u.yzx*vec3(k4,k5,k6) + u.zxy*vec3(k6,k4,k5) + k7*u.yzx*u.zxy ));

}



### *Tool ForwardSF function

Added by dcinadr on Tue, 01 Feb 2022 09:14:03 +0200