@@ -125,13 +125,13 @@ const getRay = (ndc: d.v2f) => {
125125 const invView = cameraUniform . $ . viewInv ;
126126 const invProj = cameraUniform . $ . projInv ;
127127
128- const viewPos = std . mul ( invProj , clipPos ) ;
128+ const viewPos = invProj . mul ( clipPos ) ;
129129 const viewPosNormalized = d . vec4f ( viewPos . xyz . div ( viewPos . w ) , 1.0 ) ;
130130
131- const worldPos = std . mul ( invView , viewPosNormalized ) ;
131+ const worldPos = invView . mul ( viewPosNormalized ) ;
132132
133133 const rayOrigin = invView . columns [ 3 ] . xyz ;
134- const rayDir = std . normalize ( std . sub ( worldPos . xyz , rayOrigin ) ) ;
134+ const rayDir = std . normalize ( worldPos . xyz . sub ( rayOrigin ) ) ;
135135
136136 return Ray ( {
137137 origin : rayOrigin ,
@@ -224,16 +224,34 @@ const sliderSdf3D = (position: d.v3f) => {
224224 } ) ;
225225} ;
226226
227+ const GroundParams = {
228+ groundThickness : 0.03 ,
229+ groundRoundness : 0.02 ,
230+ } ;
231+
232+ const rectangleCutoutDist = ( position : d . v2f ) => {
233+ 'use gpu' ;
234+ const groundRoundness = GroundParams . groundRoundness ;
235+
236+ return sdf . sdRoundedBox2d (
237+ position ,
238+ d . vec2f ( 1 + groundRoundness , 0.2 + groundRoundness ) ,
239+ 0.2 + groundRoundness ,
240+ ) ;
241+ } ;
242+
227243const getMainSceneDist = ( position : d . v3f ) => {
228244 'use gpu' ;
229- return sdf . opSmoothDifference (
230- sdf . sdPlane ( position , d . vec3f ( 0 , 1 , 0 ) , 0 ) ,
245+ const groundThickness = GroundParams . groundThickness ;
246+ const groundRoundness = GroundParams . groundRoundness ;
247+
248+ return sdf . opUnion (
249+ sdf . sdPlane ( position , d . vec3f ( 0 , 1 , 0 ) , 0.06 ) ,
231250 sdf . opExtrudeY (
232251 position ,
233- sdf . sdRoundedBox2d ( position . xz , d . vec2f ( 1 , 0.2 ) , 0.2 ) ,
234- 0.06 ,
235- ) ,
236- 0.01 ,
252+ - rectangleCutoutDist ( position . xz ) ,
253+ groundThickness - groundRoundness ,
254+ ) - groundRoundness ,
237255 ) ;
238256} ;
239257
@@ -384,50 +402,74 @@ const getNormal = (
384402 ) ;
385403} ;
386404
387- const getShadow = ( position : d . v3f , normal : d . v3f , lightDir : d . v3f ) => {
405+ const sqLength = ( a : d . v3f ) => {
388406 'use gpu' ;
389- const newDir = std . normalize ( lightDir ) ;
390-
391- const bias = 0.005 ;
392- const newOrigin = position . add ( normal . mul ( bias ) ) ;
393-
394- const bbox = getSliderBbox ( ) ;
395- const zDepth = d . f32 ( 0.21 ) ;
407+ return std . dot ( a , a ) ;
408+ } ;
396409
397- const sliderMin = d . vec3f ( bbox . left , bbox . bottom , - zDepth ) ;
398- const sliderMax = d . vec3f ( bbox . right , bbox . top , zDepth ) ;
410+ const getFakeShadow = (
411+ position : d . v3f ,
412+ lightDir : d . v3f ,
413+ ) : d . v3f => {
414+ 'use gpu' ;
415+ const jellyColor = jellyColorUniform . $ ;
416+ const endCapX = slider . endCapUniform . $ . x ;
399417
400- const intersection = intersectBox (
401- newOrigin ,
402- newDir ,
403- sliderMin ,
404- sliderMax ,
405- ) ;
418+ if ( position . y < - GroundParams . groundThickness ) {
419+ // Applying darkening under the ground (the shadow cast by the upper ground layer)
420+ const fadeSharpness = 30 ;
421+ const inset = 0.02 ;
422+ const cutout = rectangleCutoutDist ( position . xz ) + inset ;
423+ const edgeDarkening = std . saturate ( 1 - cutout * fadeSharpness ) ;
406424
407- if ( intersection . hit ) {
408- let t = std . max ( 0.0 , intersection . tMin ) ;
409- const maxT = intersection . tMax ;
425+ // Applying a slight gradient based on the light direction
426+ const lightGradient = std . saturate ( - position . z * 4 * lightDir . z + 1 ) ;
410427
411- for ( let i = 0 ; i < MAX_STEPS ; i ++ ) {
412- const currPos = newOrigin . add ( newDir . mul ( t ) ) ;
413- const hitInfo = getSceneDist ( currPos ) ;
428+ return d . vec3f ( 1 ) . mul ( edgeDarkening ) . mul ( lightGradient * 0.5 ) ;
429+ } else {
430+ const finalUV = d . vec2f (
431+ ( position . x - position . z * lightDir . x * std . sign ( lightDir . z ) ) *
432+ 0.5 + 0.5 ,
433+ 1 - ( - position . z / lightDir . z * 0.5 ) - 0.2 ,
434+ ) ;
435+ const data = std . textureSampleLevel (
436+ bezierTexture . $ ,
437+ filteringSampler . $ ,
438+ finalUV ,
439+ 0 ,
440+ ) ;
414441
415- if ( hitInfo . distance < SURF_DIST ) {
416- return std . select (
417- 0.8 ,
418- 0.3 ,
419- hitInfo . objectType === ObjectType . SLIDER ,
420- ) ;
421- }
442+ // Normally it would be just data.y, but there transition is too sudden when the jelly is bunched up.
443+ // To mitigate this, we transition into a position-based transition.
444+ const jellySaturation = std . mix (
445+ 0 ,
446+ data . y ,
447+ std . saturate ( position . x * 1.5 + 1.1 ) ,
448+ ) ;
449+ const shadowColor = std . mix (
450+ d . vec3f ( 0 , 0 , 0 ) ,
451+ jellyColor . xyz ,
452+ jellySaturation ,
453+ ) ;
422454
423- t += hitInfo . distance ;
424- if ( t > maxT ) {
425- break ;
426- }
427- }
455+ const contrast = 20 * std . saturate ( finalUV . y ) * ( 0.8 + endCapX * 0.2 ) ;
456+ const shadowOffset = - 0.3 ;
457+ const featherSharpness = 10 ;
458+ const uvEdgeFeather = std . saturate ( finalUV . x * featherSharpness ) *
459+ std . saturate ( ( 1 - finalUV . x ) * featherSharpness ) *
460+ std . saturate ( ( 1 - finalUV . y ) * featherSharpness ) *
461+ std . saturate ( finalUV . y ) ;
462+ const influence = std . saturate ( ( 1 - lightDir . y ) * 2 ) * uvEdgeFeather ;
463+ return std . mix (
464+ d . vec3f ( 1 ) ,
465+ std . mix (
466+ shadowColor ,
467+ d . vec3f ( 1 ) ,
468+ std . saturate ( data . x * contrast + shadowOffset ) ,
469+ ) ,
470+ influence ,
471+ ) ;
428472 }
429-
430- return d . f32 ( 0 ) ;
431473} ;
432474
433475const calculateAO = ( position : d . v3f , normal : d . v3f ) => {
@@ -463,9 +505,7 @@ const calculateLighting = (
463505 'use gpu' ;
464506 const lightDir = std . neg ( lightUniform . $ . direction ) ;
465507
466- const shadow = getShadow ( hitPosition , normal , lightDir ) ;
467- const visibility = 1.0 - shadow ;
468-
508+ const fakeShadow = getFakeShadow ( hitPosition , lightDir ) ;
469509 const diffuse = std . max ( std . dot ( normal , lightDir ) , 0.0 ) ;
470510
471511 const viewDir = std . normalize ( rayOrigin . sub ( hitPosition ) ) ;
@@ -480,10 +520,11 @@ const calculateLighting = (
480520
481521 const directionalLight = baseColor
482522 . mul ( lightUniform . $ . color )
483- . mul ( diffuse * visibility ) ;
523+ . mul ( diffuse )
524+ . mul ( fakeShadow ) ;
484525 const ambientLight = baseColor . mul ( AMBIENT_COLOR ) . mul ( AMBIENT_INTENSITY ) ;
485526
486- const finalSpecular = specular . mul ( visibility ) ;
527+ const finalSpecular = specular . mul ( fakeShadow ) ;
487528
488529 return std . saturate ( directionalLight . add ( ambientLight ) . add ( finalSpecular ) ) ;
489530} ;
@@ -629,12 +670,22 @@ const renderBackground = (
629670 ) ;
630671 const newNormal = getNormalMain ( posOffset ) ;
631672
673+ // Calculate fake bounce lighting
674+ const jellyColor = jellyColorUniform . $ ;
675+ const sqDist = sqLength ( hitPosition . sub ( d . vec3f ( endCapX , 0 , 0 ) ) ) ;
676+ const bounceLight = jellyColor . xyz . mul ( 1 / ( sqDist * 15 + 1 ) * 0.4 ) ;
677+ const sideBounceLight = jellyColor . xyz
678+ . mul ( 1 / ( sqDist * 40 + 1 ) * 0.3 )
679+ . mul ( std . abs ( newNormal . z ) ) ;
680+
632681 const litColor = calculateLighting ( posOffset , newNormal , rayOrigin ) ;
633682 const backgroundColor = applyAO (
634683 GROUND_ALBEDO . mul ( litColor ) ,
635684 posOffset ,
636685 newNormal ,
637- ) ;
686+ )
687+ . add ( d . vec4f ( bounceLight , 0 ) )
688+ . add ( d . vec4f ( sideBounceLight , 0 ) ) ;
638689
639690 const textColor = std . saturate ( backgroundColor . xyz . mul ( d . vec3f ( 0.5 ) ) ) ;
640691
0 commit comments