如何在 glsl 片段着色器中渲染多个球体
How to render multiple spheres in a glsl fragment shader
我编写了一个片段着色器,它使用简单的光线追踪来渲染片段着色器内部的球体,该片段着色器对形状进行漫反射着色。我可以让它在一个领域工作;但是,当我尝试制作多个球体时,只能看到一个。在着色器的 main() 函数中,我基本上正在执行相同的过程,为我想要制作的每个附加球体渲染一个球体,但这目前似乎不起作用。
附加信息:
球体的顶点 (x,y,z,r) 使用以下方法找到:
x 和 y 的顶点位置(即 float x = vPosition.x;
和 float y = vPosition.y;
)。 z 的顶点位置是使用下面代码中的 computeZ()
函数计算的。最后,对于 sphere.r,我使用了光线的原点顶点 (V) + 光线的方向 (W)。光线本身从原点射入屏幕,并根据它是否击中球体来确定球体的颜色(我在 raySphere()
.[=19 中有执行此功能的代码) =]
我想我只需要在 main 中创建一个数组来存储球体,然后创建一个 for 循环来为数组中的每个球体调用 raySphere()
和 shadeSphere()
函数(这是我最初尝试但没有用的方法。
主要是,在初始化一个球体的顶点位置和光线的 V 和 W 之后,我有一个 if 语句来检查 z 是否大于 0,这意味着着色器当前在球体内部,因此它可以继续着色,否则什么也不做。由于以这种方式定义球体的边界,我不确定如何使用我之前提到的 for 循环解决方案将相同的逻辑应用于多个球体。同样在 main 中,我检查 t 值(对于每个球体)是否小于 10000,如果是,则调用 shadeSphere()
函数对球体进行着色;但是,我当前的逻辑也不适用于多个领域。湍流和分形函数只是为了添加程序纹理,它们很长,所以我不会为它们添加代码。
制作多个球体的正确方法是什么?
编辑:
*根据我有一个 if 语句为每个球体设置 gl_FragColor 并且实际渲染的唯一颜色是一个这一事实,球体似乎被渲染在彼此之上在最后一条语句中设置(见代码末尾)
我有下面的相关代码:
<script src=lib1.js></script>
<body bgcolor=black>
<center>
<td><canvas id='canvas1' width=550 height=550></canvas></td>
</center>
</body>
<script id='my_vertex_shader' type='x-shader/x-vertex'>
attribute vec3 aPosition;
varying vec3 vPosition;
void main() {
gl_Position = vec4(aPosition, 1.0);
vPosition = aPosition;
}
</script>
<script id='my_fragment_shader' type='x-shader/x-fragment'>
precision mediump float;
uniform float uTime;
uniform vec3 uCursor;
varying vec3 vPosition;
vec4 sphere;
vec4 sphere2;
vec3 material;
vec3 Lrgb;
vec3 Ldir;
float computeZ(vec2 xy, float r) {
float zz = (r * r - xy.x * xy.x - xy.y * xy.y)/.5;
if (zz < 0.)
return -1.;
else
return sqrt(zz);
}
// Compute intersection of a ray with a sphere, if any. Return t.
// If there is no intersection, return 10000.
float raySphere(vec3 V, vec3 W, vec4 sph) {
//float r = 1.0;
//float b = 2.0* dot(V,W);
//float c = dot(V, V) - (sph.w * sph.w);
//float h = b*b - 4.0*c;
//float t = (-b - sqrt(h))/2.0;
//if(h <0.0 || t < 0.0 ) return 10000.;
//return t;
float b = 2.0 * dot(V -= sph.xyz, W);
float c = dot(V, V) - sph.w * sph.w;
float d = b * b - 4.0 * c;
return d < 0.0 ? 10000. : (-b - sqrt(d)) / 2.0;
}
// Diffusely shade a sphere.
// point is the x,y,z position of the surface point.
// sphere is the x,y,z,r definition of the sphere.
// material is the r,g,b color of the sphere.
//vec3 shadeSphere(vec3 point, vec4 sphere, vec3 material, float s) {
vec3 shadeSphere(vec3 point, vec4 sphere, vec3 material) {
vec3 color = vec3(1.,2.,4.);
vec3 N = (point - sphere.xyz) / sphere.w;
float diffuse = max(dot(Ldir, N), 0.0);
vec3 ambient = material/5.0;
//color = ambient + Lrgb *s *diffuse * max(0.0, dot(N , Ldir));
color = ambient + Lrgb * diffuse * max(0.0, dot(N , Ldir));
return color;
}
void main(void) {
vec2 c = uCursor.xy;
Lrgb = vec3(1.,.5,0.);
Ldir = normalize(vec3(c.x, c.y, 1. - 2. * dot(c, c)));
float x = vPosition.x;
float y = vPosition.y;
float z = computeZ(vPosition.xy, 1.0);
// COMPUTE V AND W TO CREATE THE RAY FOR THIS PIXEL,
// USING vPosition.x AND vPosition.y.
vec3 V, W;
W = normalize(vec3( 2.0,0.0,1.0 ));
vec4 spheres[3];
if(z > 0.){
//sphere = vec4(x,y,z,V + dot(W,vec3(1.,1.,1.)));
//sphere2 = vec4(x+10.,y+10.,z+10.,V + dot(W,vec3(1.,1.,1.)));
vec2 uv = vPosition.xy/uCursor.xy;
//generate a ray
//V = vec3(0.0, 1.0, 3.0);
//W = normalize(vec3((-1.0 + 2.0 )*vec2(1.78,1.0), -1.0));
//SET x,y,z AND r FOR sphere.
//SET r,g,b FOR material.
vec3 material = vec3(4., 1., 3.);
vec3 color = vec3(0., 0., 0.);
float t = 0.;
for(int i = 0; i < 3; i++){
if(i == 0){
V = vec3(2.0,1.0,.0);
spheres[i] = vec4(x,y,z/2.,V + dot(W,vec3(1.,1.,1.)));
float t = raySphere(V, W, spheres[i] );
}
if(i == 1){
V = vec3(100.0,500.0,.0);//attempt to move the vertex of the ray for the second sphere
spheres[i] = vec4(x,y,z/5.,V + dot(W,vec3(1.,1.,1.)));
vec3 shift1 = vec3(30.,30.,30.);
vec3 newPoint = shift1 + V;
float t = raySphere(newPoint, W, spheres[i] );
}
if(i == 2){
V = vec3(500.0,1.0,.0); //attempt to move the vertex of the ray for the third sphere
spheres[i] = vec4(x,y,z/7.,V + dot(W,vec3(1.,1.,1.)));
vec3 shift2 = vec3(50.,50.,50.);
vec3 newPoint2 = shift2 + V;
float t = raySphere(newPoint2, W, spheres[i] );
}
//float t2 = raySphere(V, W, sphere2);
//float s = sin((uTime));
vec3 time = vec3(uTime*2., 1.,1.);
//float s = tan((tan(sphere.z)/tan((time)*.90+200.0)));
if (t < 10000.)
//float s = (sin(sphere.x)/cos(uTime*1.123+200.0));
//if(i == 0)
color = shadeSphere(V + t * W, spheres[i], material);
//if(i == 1)
//color = shadeSphere(V + t1 * W, sphere[i], material);
//if(i == 2)
//color = shadeSphere(V + t1 * W, sphere[i], material);
//color = shadeSphere(V + t1 * W, sphere, material,s);
//if (t2 < 10000.)
//color = shadeSphere(V + t2 * W, sphere, material,s);
color.r = 0.5;
color = pow(color, vec3(.45,.45,.45)); // Do Gamma correction.
//float d = dot(vec3(x,y,z), vec3(1.,1.,1.));
//if (d > 0.)
// s += 0.6 * d;
//gl_FragColor = vec4(color, 1.); // Set opa city to 1.
if(i == 0)
gl_FragColor = vec4((color) * vec3(5.0, 1.0, 4.5), 1.);
if(i == 1)
gl_FragColor = vec4((color) * vec3(1.0, 3.0, 7.5), 1.);
if(i == 2)
gl_FragColor = vec4((color) * vec3(3.0, 4.0, 8.5), 1.);
}
} //Close brace for z-check
}
</script>
球体 3 的图像似乎渲染在其他两个球体之上
所以如果我做对了:
- 你的所有球体都在与投影平面共面的同一平面上
- 所有球体都在半径
r=1.0
- 推导自:
float z = computeZ(vPosition.xy, 1.0);
vPosition
是从vertex shader传来的
- 并包含片段的插值屏幕位置
需要清除的未知事物:
什么以及如何传递给 GL?
我得到的印象是你为每个球体传递了一个顶点(作为哪个基元?)这显然是错误的,除非你有几何着色器。但这很难说,因为您的问题没有相关信息。没有制服,没有属性,没有任何插值器,也没有连接到 GLSL
的代码
怎么做?
我看到 2 个基本选项:
通过覆盖整个屏幕的单个四边形
这样片段着色器将循环遍历屏幕的所有像素,因此您需要某种数组,每个球体 x,y,r
每个片段调用都存在。我所知道的唯一可靠的方法是使用纹理
- 您可以创建 1D RGB 纹理,其中
x=B; y=G; r=R;
- 或 3x 1D 浮动纹理一个用于
x,y,r
- 或 1x 1D 浮动纹理,球体被打包成 3 个纹素...
然后在每个片段调用循环遍历所有球体...
每个球体传递一个四边形
因此您可以使用端点 (x0,y0),(x1,y1)
代替每个球体来渲染 2D Quad。
- 四边形的中间是球心
(x,y) = ( 0.5*(x0+x1) , 0.5*(y0+y1) )
- 球体的半径是
r = 0.5*|x1-x0| = 0.5*|y1-y0|
因此在顶点着色器中计算 x,y,r
并将其传递给片段着色器
[备注]
这两种方式各有千秋pros/cons选择适合自己的
- 考虑到您的性能需求
- 以及什么更接近您的做事方式...
看这里Draw Quadratic Curve on GPU这是非常相似的任务
我编写了一个片段着色器,它使用简单的光线追踪来渲染片段着色器内部的球体,该片段着色器对形状进行漫反射着色。我可以让它在一个领域工作;但是,当我尝试制作多个球体时,只能看到一个。在着色器的 main() 函数中,我基本上正在执行相同的过程,为我想要制作的每个附加球体渲染一个球体,但这目前似乎不起作用。
附加信息:
球体的顶点 (x,y,z,r) 使用以下方法找到:
x 和 y 的顶点位置(即 float x = vPosition.x;
和 float y = vPosition.y;
)。 z 的顶点位置是使用下面代码中的 computeZ()
函数计算的。最后,对于 sphere.r,我使用了光线的原点顶点 (V) + 光线的方向 (W)。光线本身从原点射入屏幕,并根据它是否击中球体来确定球体的颜色(我在 raySphere()
.[=19 中有执行此功能的代码) =]
我想我只需要在 main 中创建一个数组来存储球体,然后创建一个 for 循环来为数组中的每个球体调用 raySphere()
和 shadeSphere()
函数(这是我最初尝试但没有用的方法。
主要是,在初始化一个球体的顶点位置和光线的 V 和 W 之后,我有一个 if 语句来检查 z 是否大于 0,这意味着着色器当前在球体内部,因此它可以继续着色,否则什么也不做。由于以这种方式定义球体的边界,我不确定如何使用我之前提到的 for 循环解决方案将相同的逻辑应用于多个球体。同样在 main 中,我检查 t 值(对于每个球体)是否小于 10000,如果是,则调用 shadeSphere()
函数对球体进行着色;但是,我当前的逻辑也不适用于多个领域。湍流和分形函数只是为了添加程序纹理,它们很长,所以我不会为它们添加代码。
制作多个球体的正确方法是什么?
编辑:
*根据我有一个 if 语句为每个球体设置 gl_FragColor 并且实际渲染的唯一颜色是一个这一事实,球体似乎被渲染在彼此之上在最后一条语句中设置(见代码末尾)
我有下面的相关代码:
<script src=lib1.js></script>
<body bgcolor=black>
<center>
<td><canvas id='canvas1' width=550 height=550></canvas></td>
</center>
</body>
<script id='my_vertex_shader' type='x-shader/x-vertex'>
attribute vec3 aPosition;
varying vec3 vPosition;
void main() {
gl_Position = vec4(aPosition, 1.0);
vPosition = aPosition;
}
</script>
<script id='my_fragment_shader' type='x-shader/x-fragment'>
precision mediump float;
uniform float uTime;
uniform vec3 uCursor;
varying vec3 vPosition;
vec4 sphere;
vec4 sphere2;
vec3 material;
vec3 Lrgb;
vec3 Ldir;
float computeZ(vec2 xy, float r) {
float zz = (r * r - xy.x * xy.x - xy.y * xy.y)/.5;
if (zz < 0.)
return -1.;
else
return sqrt(zz);
}
// Compute intersection of a ray with a sphere, if any. Return t.
// If there is no intersection, return 10000.
float raySphere(vec3 V, vec3 W, vec4 sph) {
//float r = 1.0;
//float b = 2.0* dot(V,W);
//float c = dot(V, V) - (sph.w * sph.w);
//float h = b*b - 4.0*c;
//float t = (-b - sqrt(h))/2.0;
//if(h <0.0 || t < 0.0 ) return 10000.;
//return t;
float b = 2.0 * dot(V -= sph.xyz, W);
float c = dot(V, V) - sph.w * sph.w;
float d = b * b - 4.0 * c;
return d < 0.0 ? 10000. : (-b - sqrt(d)) / 2.0;
}
// Diffusely shade a sphere.
// point is the x,y,z position of the surface point.
// sphere is the x,y,z,r definition of the sphere.
// material is the r,g,b color of the sphere.
//vec3 shadeSphere(vec3 point, vec4 sphere, vec3 material, float s) {
vec3 shadeSphere(vec3 point, vec4 sphere, vec3 material) {
vec3 color = vec3(1.,2.,4.);
vec3 N = (point - sphere.xyz) / sphere.w;
float diffuse = max(dot(Ldir, N), 0.0);
vec3 ambient = material/5.0;
//color = ambient + Lrgb *s *diffuse * max(0.0, dot(N , Ldir));
color = ambient + Lrgb * diffuse * max(0.0, dot(N , Ldir));
return color;
}
void main(void) {
vec2 c = uCursor.xy;
Lrgb = vec3(1.,.5,0.);
Ldir = normalize(vec3(c.x, c.y, 1. - 2. * dot(c, c)));
float x = vPosition.x;
float y = vPosition.y;
float z = computeZ(vPosition.xy, 1.0);
// COMPUTE V AND W TO CREATE THE RAY FOR THIS PIXEL,
// USING vPosition.x AND vPosition.y.
vec3 V, W;
W = normalize(vec3( 2.0,0.0,1.0 ));
vec4 spheres[3];
if(z > 0.){
//sphere = vec4(x,y,z,V + dot(W,vec3(1.,1.,1.)));
//sphere2 = vec4(x+10.,y+10.,z+10.,V + dot(W,vec3(1.,1.,1.)));
vec2 uv = vPosition.xy/uCursor.xy;
//generate a ray
//V = vec3(0.0, 1.0, 3.0);
//W = normalize(vec3((-1.0 + 2.0 )*vec2(1.78,1.0), -1.0));
//SET x,y,z AND r FOR sphere.
//SET r,g,b FOR material.
vec3 material = vec3(4., 1., 3.);
vec3 color = vec3(0., 0., 0.);
float t = 0.;
for(int i = 0; i < 3; i++){
if(i == 0){
V = vec3(2.0,1.0,.0);
spheres[i] = vec4(x,y,z/2.,V + dot(W,vec3(1.,1.,1.)));
float t = raySphere(V, W, spheres[i] );
}
if(i == 1){
V = vec3(100.0,500.0,.0);//attempt to move the vertex of the ray for the second sphere
spheres[i] = vec4(x,y,z/5.,V + dot(W,vec3(1.,1.,1.)));
vec3 shift1 = vec3(30.,30.,30.);
vec3 newPoint = shift1 + V;
float t = raySphere(newPoint, W, spheres[i] );
}
if(i == 2){
V = vec3(500.0,1.0,.0); //attempt to move the vertex of the ray for the third sphere
spheres[i] = vec4(x,y,z/7.,V + dot(W,vec3(1.,1.,1.)));
vec3 shift2 = vec3(50.,50.,50.);
vec3 newPoint2 = shift2 + V;
float t = raySphere(newPoint2, W, spheres[i] );
}
//float t2 = raySphere(V, W, sphere2);
//float s = sin((uTime));
vec3 time = vec3(uTime*2., 1.,1.);
//float s = tan((tan(sphere.z)/tan((time)*.90+200.0)));
if (t < 10000.)
//float s = (sin(sphere.x)/cos(uTime*1.123+200.0));
//if(i == 0)
color = shadeSphere(V + t * W, spheres[i], material);
//if(i == 1)
//color = shadeSphere(V + t1 * W, sphere[i], material);
//if(i == 2)
//color = shadeSphere(V + t1 * W, sphere[i], material);
//color = shadeSphere(V + t1 * W, sphere, material,s);
//if (t2 < 10000.)
//color = shadeSphere(V + t2 * W, sphere, material,s);
color.r = 0.5;
color = pow(color, vec3(.45,.45,.45)); // Do Gamma correction.
//float d = dot(vec3(x,y,z), vec3(1.,1.,1.));
//if (d > 0.)
// s += 0.6 * d;
//gl_FragColor = vec4(color, 1.); // Set opa city to 1.
if(i == 0)
gl_FragColor = vec4((color) * vec3(5.0, 1.0, 4.5), 1.);
if(i == 1)
gl_FragColor = vec4((color) * vec3(1.0, 3.0, 7.5), 1.);
if(i == 2)
gl_FragColor = vec4((color) * vec3(3.0, 4.0, 8.5), 1.);
}
} //Close brace for z-check
}
</script>
球体 3 的图像似乎渲染在其他两个球体之上
所以如果我做对了:
- 你的所有球体都在与投影平面共面的同一平面上
- 所有球体都在半径
r=1.0
- 推导自:
float z = computeZ(vPosition.xy, 1.0);
- 推导自:
vPosition
是从vertex shader传来的- 并包含片段的插值屏幕位置
需要清除的未知事物:
什么以及如何传递给 GL?
我得到的印象是你为每个球体传递了一个顶点(作为哪个基元?)这显然是错误的,除非你有几何着色器。但这很难说,因为您的问题没有相关信息。没有制服,没有属性,没有任何插值器,也没有连接到 GLSL
的代码
怎么做?
我看到 2 个基本选项:
通过覆盖整个屏幕的单个四边形
这样片段着色器将循环遍历屏幕的所有像素,因此您需要某种数组,每个球体
x,y,r
每个片段调用都存在。我所知道的唯一可靠的方法是使用纹理- 您可以创建 1D RGB 纹理,其中
x=B; y=G; r=R;
- 或 3x 1D 浮动纹理一个用于
x,y,r
- 或 1x 1D 浮动纹理,球体被打包成 3 个纹素...
然后在每个片段调用循环遍历所有球体...
- 您可以创建 1D RGB 纹理,其中
每个球体传递一个四边形
因此您可以使用端点
(x0,y0),(x1,y1)
代替每个球体来渲染 2D Quad。- 四边形的中间是球心
(x,y) = ( 0.5*(x0+x1) , 0.5*(y0+y1) )
- 球体的半径是
r = 0.5*|x1-x0| = 0.5*|y1-y0|
因此在顶点着色器中计算
x,y,r
并将其传递给片段着色器- 四边形的中间是球心
[备注]
这两种方式各有千秋pros/cons选择适合自己的
- 考虑到您的性能需求
- 以及什么更接近您的做事方式...
看这里Draw Quadratic Curve on GPU这是非常相似的任务