我正在尝试将这种渲染草的技术应用到我的three.js应用程序中。
http://davideprati.com/demo/grass/
在0号位置的水平地形上,一切看起来都非常棒!
问题是,我的应用程序(游戏)的地形由高度图修改,因此该地形上的极少数(如果有)位置位于y位置。
看起来这个顶点着色器动画代码假设草对象位于y位置0,以便下面的顶点着色器代码按预期工作:
if (pos.y > 1.0) {
float noised = noise(pos.xy);
pos.y += sin(globalTime * magnitude * noised);
pos.z += sin(globalTime * magnitude * noised);
if (pos.y > 1.7){
pos.x += sin(globalTime * noised);
}
}
这种情况假设地形是平坦的并且位于0处,因此只有地面上方的顶点才有动画效果。好吧..嗯..因为所有顶点都高于1,高度图(大多数情况下),会出现一些奇怪的效果,例如草地滑动到整个地方大声笑。
有没有办法做到这一点,我可以更多地根据精灵指定y位置阈值而不是世界位置?还是有更好的方法来处理这个“滑倒”的问题?
对于着色器代码=]
,我是一个极端的noobie非常感谢任何帮助。
我不知道我在做什么。
编辑*好吧,我认为问题是我正在根据它所在的地形的y位置改变合并到主草容器几何体中的每个网格的y位置。我想着色器正在查看局部位置,但由于几何体本身垂直位移,着色器不知道如何补偿。嗯...
好的,我做了一个演示问题的小提琴: https://jsfiddle.net/titansoftime/a3xr8yp7/
将#128行上的值更改为1而不是2,一切都很好。不确定如何解决这个问题。
另外,我不知道为什么颜色会这样做,它们在我的应用程序中看起来很好。
答案 0 :(得分:1)
如果我正确理解了这个问题:
你是在寻求“本地”的立场。可以说,单股草是一条狭长的条带,有一些高度段。
如果您希望这种模块化,易于扩展等,这很可能会在0-1范围内向某个方向延伸。假设它沿着该方向有四个段,这将产生坐标为[0.0,0.333,0.666,1.0]的顶点。它比任意范围稍微有点意义,因为很容易推断0是磨削的,1是刀尖。
这是“本地”或模型空间。将此值与modelMatrix
相乘后,您将其转换为世界空间(称之为localToWorld
)。
在着色器中,它可能看起来像这样
void main(){
vec4 localPosition = vec4( position, 1.);
vec4 worldPosition = modelMatrix * localPosition;
vec4 viewPosition = viewMatrix * worldPosition;
vec4 projectedPosition = projectionMatrix * viewPosition; //either orthographic or perspective
gl_Position = projectedPosition;
}
这是您转换的经典“你有一个场景图节点”。根据您为网格position
设置的内容,rotation
和scale
vec4 worldPosition
会有所不同,但本地位置始终相同。如果某些东西是底部或顶部,你无法单独判断该值,任何值都是可行的,因为你的地形可以是任何东西。
使用这种方法,您可以编写着色器和逻辑,说明如果顶点高度为0(或小于某个epsilon),则不进行动画处理。
所以这给我们带来了一些逻辑,它在一些假定的空间中起作用(你有1.0和1.7的规则)。
因为您正在翻译几何图形并将它们合并,所以您不再具有此用户友好空间即模型空间。现在这些刀片很可能会跳过local2world
转换(它很可能最终只是一个单位矩阵)。
这明显地弄乱了你选择顶点的逻辑。
如果您必须采用分配它们的方法,那么您需要另一个频道来表示该局部空间的含义,即使您只将它用于该动画。
已存在两个合适的通道 - 紫外线和顶点颜色。 Uv你可以想象在另一个空间中有另一个平面网格,它映射到你正在渲染的网格。但在这种特殊情况下,您似乎可以使用自定义属性aBladeHeight
,例如浮点数。
void main(){
vec4 worldPosition = vec4(position, 1.); //you "burnt/baked" this transformation in, so no need to go from local to world in the shader
vec2 localPosition = uv; //grass in 2d, not transformed to your terrain
//this check knows whats on the bottom of the grass
//rather than whats on the ground (has no idea where the ground is)
if(localPosition.y){
//since local does not exist, the only space we work in is world
//we apply the transformation in that space, but the filter
//is the check above, in uv space, where we know whats the bottom, whats the top
worldPosition.xy += myLogic();
}
gl_Position = projectionMatrix * viewMatrix * worldPosition;
}
模仿“本地空间”
void main(){
vec4 localSpace = vec4(uv,0.,1.);
gl_Position = projectionMatrix * modelViewMatrix * localSpace;
}
所有刀片都会互相重叠。
修改强>
通过实例化,着色器看起来像这样:
attribute vec4 aInstanceMatrix0; //16 floats to encode a matrix4
attribute vec4 aInstanceMatrix1;
attribute vec4 aInstanceMatrix2;
//attribute vec4 aInstanceMatrix3; //but one you know will be 0,0,0,1 so you can pack in the first 3
void main(){
vec4 localPos = vec4(position, 1.); //the local position is intact, its the normalized 0-1 blade
//do your thing in local space
if(localPos.y > foo){
localPos.xz += myLogic();
}
//notice the difference, instead of using the modelMatrix, you use the instance attributes in it's place
mat4 localToWorld = mat4(
aInstanceMatrix0,
aInstanceMatrix1,
aInstanceMatrix2,
//aInstanceMatrix3
0. , 0. , 0. , 1. //this is actually wrong i think, it should be the last column not row, but for illustrative purposes,
);
//to pack it more effeciently the rows would look like this
// xyz w
// xyz w
// xyz w
// 000 1
// off the top of my head i dont know what the correct code is
mat4 foo = mat4(
aInstanceMatrix0.xyz, 0.,
aInstanceMatrix1.xyz, 0.,
aInstanceMatrix2.xyz, 0.,
aInstanceMatrix0.w, aInstanceMatrix1.w, aInstanceMatrix2.w, 1.
)
//you can still use the modelMatrix with this if you want to move the ENTIRE hill with all the grass with .position.set()
vec4 worldPos = localToWorld * localPos;
gl_Position = projectionMatrix * viewMatrix * worldPos;
}