光线跟踪MaxScript版
看到一篇JavaScript写的光线跟踪,于是我想搬进MaxScript。
JavaScript的帖子可见Milo的博客 http://www.cnblogs.com/miloyip/archive/2010/03/29/1698953.html
先从画布填充开始.
FreeSceneBitmaps()
if theCanvas!= undefined do UnDisplay theCanvas
CanvasWidth =256
CanvasHeight =256
theCanvas = bitmap CanvasWidth CanvasHeight color:white
for PosY =0.0 to CanvasHeight -1do
for PosX =0.0 to CanvasWidth -1do
(
R = (PosX / CanvasWidth) *255
G = (PosY / CanvasHeight) *255
B =0
SetPixels theCanvas [PosX,PosY] #((color R G B ))
)
Display theCanvas
FreeSceneBitmaps()
if theCanvas!= undefined do UnDisplay theCanvas
CanvasWidth =256
CanvasHeight =256
theCanvas = bitmap CanvasWidth CanvasHeight color:white
for PosY =0.0 to CanvasHeight -1do
for PosX =0.0 to CanvasWidth -1do
(
checkWidth =16
XI = PosX as Integer/checkWidth
YI = PosY as Integer/checkWidth
theState = mod XI 2== mod YI 2
if theState do
(
SetPixels theCanvas [PosX,PosY] #(black)
)
)
Display theCanvas
GC()与FreeSceneBitmaps()是MaxScript用于释放内存的函数,后者针对位图的。bitmap和SetPixels可以在MaxScript参考中搜索 BitMap。
射线与圆球相交:
MaxScript中已经有Point3、Ray,以及两种球形几何体,Sphere和GeoSphere;还有IntersectRay、IntersectRayEX、IntersectRayScene来做各种相交检测及获取信息。这里暂不使用MaxScript内置的相交检测。过去的博客里面有配图版
http://cubevfx.blog.163.com/blog/static/127492421201052482756123/
(
Geometry = undefined,
Distance =0.0,
Pos= [0,0,0],
Normal = [0,0,0]
)
Fn IntersectRaySphere theRay theSphere =
(
RayResult = IntersectRayResult()
helperVector = theSphere.Pos - theRay.Pos
helperValue = Dot helperVector theRay.Dir
theRayShadow = theRay.Dir * HelperValue
theEdgeLength = (theSphere.Radius ^2) - (distance helperVector theRayShadow) ^2
if theEdgeLength >=0 then
(
theEdgeLengthSqrtRoot = sqrt theEdgeLength
if (distance theRay.Pos theSphere.Pos) < theSphere.Radius then
(
helperValue += theEdgeLengthSqrtRoot
)
else
(
helperValue -= theEdgeLengthSqrtRoot
)
RayResult.Geometry = theSphere
RayResult.Pos = theRay.Dir * helperValue + theRay.Pos
RayResult.Distance = Distance RayResult.Pos theRay.Pos
RayResult.Normal = RayResult.Pos - theSphere.Pos
)
RayResult
)
Global theSpline,theSphere,thePoint
if not IsValidNode theSpline do
(
theSpline = SplineShape()
AddNewSpline theSpline
AddKnot theSpline 1 #corner #line [0,0,0]
AddKnot theSpline 1 #corner #line [0,0,100]
UpdateShape theSpline
theSpline.Pos = [-100,0,0]
)
if not IsValidNode theSphere do theSphere = Sphere ReCenter:off
if not IsValidNode thePoint do thePoint = Point WireColor:Green Box:on axistripod:off centermarker:off constantscreensize:off drawontop:off
theRay = ray theSpline.Pos theSpline.Dir
RayResult = IntersectRaySphere theRay theSphere
thePoint.Pos = RayResult.Pos
CompleteRedraw()
Global theSpline,theSphere,thePoint 一行开始,为建立测试函数用的场景。 IsValidNode用于检测一个物体是否有效。MaxScript自身的Ray需要两个参数,位置和方向,而每个Max的物体,都有位置和方向属性,方向即物体自身坐标z轴的朝向。测试场景使用一条直线的位置和方向建立射线,并将一个虚拟体位置更改到交点,现在可以随意旋转直线反复运行这段脚本。
有了圆与射线的相交,就可以渲染深度图。
(
Fn WrapperCamera TheCamera =
(
local NewCamera,tempCamera,CreateRay
Struct NewCamera
(
Pos,Front,Up,Right,TM,Fov,FovScale,
fn GenerateRay canvX canvY =
(
local camX,camY,rayDir
camX = right * ((canvX -0.5) * fovScale)
camY = UP * ((canvY -0.5) * fovScale)
rayDir = Normalize (Front + camX + camY)
ray Pos rayDir
)
)
tempCamera = NewCamera()
tempCamera.Pos = theCamera.Pos
tempCamera.TM = theCamera.Transform
tempCamera.Front = Normalize (-theCamera.Transform.Row3)
tempCamera.UP = Normalize theCamera.Transform.Row2
tempCamera.Right = Normalize theCamera.Transform.Row1
tempCamera.Fov = theCamera.Fov
tempCamera.FovScale = (tan (theCamera.Fov *0.5)) *2
tempCamera
),
SphereList =for tGeo in Geometry where
(
ClassOf tGeo == Sphere or ClassOf tGeo == GeoSphere
) collect tGeo,
CameraList =for tCam in Cameras collect WrapperCamera tCam
)
Struct IntersectRayResult
(
Geometry = undefined,
Distance =0.0,
Pos= [0,0,0],
Normal = [0,0,0]
)
Fn IntersectRaySphere theRay theSphere =
(
RayResult = IntersectRayResult()
helperVector = theSphere.Pos - theRay.Pos
helperValue = Dot helperVector theRay.Dir
theRayShadow = theRay.Dir * HelperValue
theEdgeLength = (theSphere.Radius ^2) - (distance helperVector theRayShadow) ^2
if theEdgeLength >=0 then
(
theEdgeLengthSqrtRoot = sqrt theEdgeLength
if (distance theRay.Pos theSphere.Pos) < theSphere.Radius then
(
helperValue += theEdgeLengthSqrtRoot
)
else
(
helperValue -= theEdgeLengthSqrtRoot
)
RayResult.Geometry = theSphere
RayResult.Pos = theRay.Dir * helperValue + theRay.Pos
RayResult.Distance = Distance RayResult.Pos theRay.Pos
RayResult.Normal = Normalize( RayResult.Pos - theSphere.Pos)
)
RayResult
)
Fn RenderDepth theCanvas theScene maxDepth =
(
local canvasHeight , canvasWidth,theCamera
canvasHeight = theCanvas.Height -1
canvasWidth = theCanvas.Width -1
theCamera = theScene.CameraList[1]
for canvY =0.0 to canvasHeight do
(
screenY =1- canvY/theCanvas.Height
for canvX =0.0 to canvasWidth do
(
screenX = canvX/theCanvas.Width
theRay = theCamera.GenerateRay screenX screenY
rayResults =for tObj in theScene.SphereList collect IntersectRaySphere theRay tObj
rayResults =for tRel in rayResults where tRel.Geometry != undefined collect tRel
rayDepths =for tRel in rayResults collect tRel.Distance
firstID = FindItem rayDepths (aMin rayDepths)
if firstID !=0do
(
theRayResult = rayResults[firstID]
resultDepth = (theRayResult.Distance / maxDepth) *255
depth =255- (if resultDepth <255 then resultDepth else255)
R = G = B = depth
SetPixels theCanvas [canvX,canvY] #((color R G B ))
)
)
)
Display theCanvas
)
/*
FreeCamera Pos:[0,0,100]
Sphere()
*/
theScene = SceneInfo()
GC()
FreeSceneBitmaps()
if theCanvas!= undefined do UnDisplay theCanvas
CanvasWidth =256
CanvasHeight =256
theCanvas = Bitmap CanvasWidth CanvasHeight color:black
RenderDepth theCanvas theScene 100
也可以渲染法向量
(
Fn WrapperCamera TheCamera =
(
local NewCamera,tempCamera,CreateRay
Struct NewCamera
(
Pos,Front,Up,Right,TM,Fov,FovScale,
fn GenerateRay canvX canvY =
(
local camX,camY,rayDir
camX = right * ((canvX -0.5) * fovScale)
camY = UP * ((canvY -0.5) * fovScale)
rayDir = Normalize (Front + camX + camY)
ray Pos rayDir
)
)
tempCamera = NewCamera()
tempCamera.Pos = theCamera.Pos
tempCamera.TM = theCamera.Transform
tempCamera.Front = Normalize (-theCamera.Transform.Row3)
tempCamera.UP = Normalize theCamera.Transform.Row2
tempCamera.Right = Normalize theCamera.Transform.Row1
tempCamera.Fov = theCamera.Fov
tempCamera.FovScale = (tan (theCamera.Fov *0.5)) *2
tempCamera
),
SphereList =for tGeo in Geometry where
(
ClassOf tGeo == Sphere or ClassOf tGeo == GeoSphere
) collect tGeo,
CameraList =for tCam in Cameras collect WrapperCamera tCam
)
Struct IntersectRayResult
(
Geometry = undefined,
Distance =0.0,
Pos= [0,0,0],
Normal = [0,0,0]
)
Fn IntersectRaySphere theRay theSphere =
(
RayResult = IntersectRayResult()
helperVector = theSphere.Pos - theRay.Pos
helperValue = Dot helperVector theRay.Dir
theRayShadow = theRay.Dir * HelperValue
theEdgeLength = (theSphere.Radius ^2) - (distance helperVector theRayShadow) ^2
if theEdgeLength >=0 then
(
theEdgeLengthSqrtRoot = sqrt theEdgeLength
if (distance theRay.Pos theSphere.Pos) < theSphere.Radius then
(
helperValue += theEdgeLengthSqrtRoot
)
else
(
helperValue -= theEdgeLengthSqrtRoot
)
RayResult.Geometry = theSphere
RayResult.Pos = theRay.Dir * helperValue + theRay.Pos
RayResult.Distance = Distance RayResult.Pos theRay.Pos
RayResult.Normal = Normalize( RayResult.Pos - theSphere.Pos)
)
RayResult
)
Fn RenderDepth theCanvas theScene maxDepth =
(
local canvasHeight , canvasWidth,theCamera
canvasHeight = theCanvas.Height -1
canvasWidth = theCanvas.Width -1
theCamera = theScene.CameraList[1]
for canvY =0.0 to canvasHeight do
(
screenY =1- canvY/theCanvas.Height
for canvX =0.0 to canvasWidth do
(
screenX = canvX/theCanvas.Width
theRay = theCamera.GenerateRay screenX screenY
rayResults =for tObj in theScene.SphereList collect IntersectRaySphere theRay tObj
rayResults =for tRel in rayResults where tRel.Geometry != undefined collect tRel
rayDepths =for tRel in rayResults collect tRel.Distance
firstID = FindItem rayDepths (aMin rayDepths)
if firstID !=0do
(
theRayResult = rayResults[firstID]
theNormal = theRayResult.Normal
R = (theNormal.X +1 ) *128
G = (theNormal.Y +1 ) *128
B = (theNormal.Z +1 ) *128
SetPixels theCanvas [canvX,canvY] #((color R G B ))
)
)
)
Display theCanvas
)
/*
FreeCamera Pos:[0,0,100]
Sphere()
*/
theScene = SceneInfo()
GC()
FreeSceneBitmaps()
if theCanvas!= undefined do UnDisplay theCanvas
CanvasWidth =256
CanvasHeight =256
theCanvas = Bitmap CanvasWidth CanvasHeight color:black
RenderDepth theCanvas theScene 100
以上两段代码中的
/*
FreeCamera Pos:[0,0,100]
Sphere()
*/
为建立场景的MaxScript,也可以手动建立物体调整位置。
下面是渲染Phong材质,对前面的代码进行了修改,SceneInfo中SphereList更改为GeometryList,光线求交的函数也放在Plane和Sphere的结构内。此外,摄像机的代码里没有做旋转=。。=,需要手动去旋转摄像机,MaxScript里面默认情况下修改物体的rotation属性是以世界中心为坐标轴,想要以自身为坐标可以到MaxScript自带手册中查找coordsys,手册中有范例。
(
Dir,
Color,
Fn CreateByLight theLight = LightInfo theLight.Dir theLight.RGB
)
Struct PhongInfo
(
Diffuse,Specular,Shiniess,
Reflectiveness =0.5,
Fn Sample theRay thePos theNormal theLight =
(
normalDotLight = dot theNormal theLight.Dir
H = Normalize (theLight.Dir - theRay.Dir)
normalDotH = dot theNormal H
diffuseTerm = Diffuse * (aMax #(normalDotH ,0))
specularTerm = Specular * (pow (aMax #(normalDotH ,0)) Shiniess)
theLight.Color * (diffuseTerm + specularTerm)
),
Fn CreateByMaterial theMat = PhongInfo theMat.Diffuse theMat.Specular theMat.glossiness
)
Struct CheckerMaterial
(
Scale ,Reflectiveness =0.5,
Fn Sample theRay thePos theNormal NullArg =
(
theFloorPosX = (Floor thePos.X * Scale) as integer
theFloorPosY = (Floor thePos.Y * Scale) as integer
State1 = (thePos.X >0 and thePos.Y >0) or (thePos.X <0 and thePos.Y <0)
State2 = abs (mod theFloorPosX 2) == abs (mod theFloorPosY 2)
if State1 then
(
if State2 then black else white
)
else
(
if State2 then white else black
)
)
)
Struct CameraInfo
(
Pos,Front,Up,Right,TM,Fov,FovScale,
fn GenerateRay canvX canvY =
(
local camX,camY,rayDir
camX = right * ((canvX -0.5) * fovScale)
camY = UP * ((canvY -0.5) * fovScale)
rayDir = Normalize (Front + camX + camY)
ray Pos rayDir
)
)
Struct IntersectRayResult
(
Geometry = undefined,
Distance =0.0,
Pos= [0,0,0],
Normal = [0,0,0]
)
Struct PlaneInfo
(
Pos,Normal,Material,
Fn IntersectRay theRay thePlane =
(
RayResult = IntersectRayResult()
RayDir_Dot_Normal = dot theRay.Dir Normal
if RayDir_Dot_Normal <0do
(
helperValue = dot Normal (theRay.Pos - Pos)
RayResult.Geometry = thePlane
RayResult.Distance =-helperValue/RayDir_Dot_Normal
RayResult.Pos = theRay.Pos + theRay.Dir * RayResult.Distance
RayResult.Normal = thePlane.Normal
)
RayResult
)
)
Struct SphereInfo
(
Radius,
Pos,
Material,
Fn IntersectRay theRay theSphere =
(
RayResult = IntersectRayResult()
helperVector = theSphere.Pos - theRay.Pos
helperValue = Dot helperVector theRay.Dir
theRayShadow = theRay.Dir * HelperValue
theEdgeLength = (theSphere.Radius ^2) - (distance helperVector theRayShadow) ^2
if theEdgeLength >=0 then
(
theEdgeLengthSqrtRoot = sqrt theEdgeLength
if (distance theRay.Pos theSphere.Pos) < theSphere.Radius then
(
helperValue += theEdgeLengthSqrtRoot
)
else
(
helperValue -= theEdgeLengthSqrtRoot
)
RayResult.Geometry = theSphere
RayResult.Pos = theRay.Dir * helperValue + theRay.Pos
RayResult.Distance = Distance RayResult.Pos theRay.Pos
RayResult.Normal = Normalize( RayResult.Pos - theSphere.Pos)
)
RayResult
),
Fn CreateBySphere theSphere =
(
if theSphere.Material == undefined do theSphere.Material = Standard()
SphereInfo theSphere.Radius theSphere.Pos (PhongInfo.CreateByMaterial theSphere.Material)
)
)
Struct SceneInfo
(
Fn WrapperCamera TheCamera =
(
local tempCamera,CreateRay
tempCamera = CameraInfo()
tempCamera.Pos = theCamera.Pos
tempCamera.TM = theCamera.Transform
tempCamera.Front = Normalize (-theCamera.Transform.Row3)
tempCamera.UP = Normalize theCamera.Transform.Row2
tempCamera.Right = Normalize theCamera.Transform.Row1
tempCamera.Fov = theCamera.Fov
tempCamera.FovScale = (tan (theCamera.Fov *0.5)) *2
tempCamera
),
LightList =for tLight in Lights collect LightInfo.CreateByLight tLight ,
GeometryList =
(
for tGeo in Geometry where
(
ClassOf tGeo == Sphere or ClassOf tGeo == GeoSphere or ClassOf tGeo == Plane
)
collect
(
if ClassOf tGeo == Plane then PlaneInfo tGeo.Pos tGeo.Dir (CheckerMaterial 0.1)
else SphereInfo.CreateBySphere tGeo
)
),
CameraList =for tCam in Cameras collect WrapperCamera tCam
)
Fn RenderRayTrace theCanvas theScene =
(
local canvasHeight , canvasWidth,theCamera
canvasHeight = theCanvas.Height -1
canvasWidth = theCanvas.Width -1
theCamera = theScene.CameraList[1]
theLight = theScene.LightList[1]
for canvY =0.0 to canvasHeight do
(
screenY =1- canvY/theCanvas.Height
for canvX =0.0 to canvasWidth do
(
screenX = canvX/theCanvas.Width
theRay = theCamera.GenerateRay screenX screenY
rayResults =for tObj in theScene.GeometryList collect tObj.IntersectRay theRay tObj
rayResults =for tRel in rayResults where tRel.Geometry != undefined collect tRel
rayDepths =for tRel in rayResults collect tRel.Distance
firstID = FindItem rayDepths (aMin rayDepths)
if firstID !=0do
(
theRayResult = rayResults[firstID]
theColor = theRayResult.Geometry.Material.Sample theRay theRayResult.Pos theRayResult.Normal theLight
SetPixels theCanvas [canvX,canvY] #(theColor)
)
)
)
Display theCanvas
)
/* Create Scene
Directionallight Pos:[50,50,50] Rotation:(random (quat 0 0 1 1) (quat 1 0 0 1))
FreeCamera Pos:[0,0,100]
(Sphere()).Material = MeditMaterials[1]
Plane Width:150 Length:150
MeditMaterials[1].Diffuse = random [0,0,0] [255,255,255]
*/
theScene = SceneInfo()
GC()
FreeSceneBitmaps()
if theCanvas!= undefined do UnDisplay theCanvas
CanvasWidth =256
CanvasHeight =256
theCanvas = Bitmap CanvasWidth CanvasHeight color:black
RenderRayTrace theCanvas theScene