2
\$\begingroup\$

I would like to create a game similar to Scanner Sombre. The player is in a dark environment and can't see their surrounding unless they use a scanner. That scanner sends out rays which mark little dots on all surfaces, creating 3D map of environment.

I tried to just paint visible dots on transparent textures, but that does not work because the dots are supposed to always face the player.

I considered to instantiate a new game object for every single dot. But as the game progress, the player will see billions of these dots at once, and that would likely be terrible in terms of performance. Can someone suggest a possible technique to do this?

\$\endgroup\$
0

4 Answers 4

2
\$\begingroup\$

One option would be to use a particle system to host all the dots. Particle systems are optimized for rendering a large number of very simple billboard objects. That means a particle in a ParticleSystem generates far less management overhead than a full-fledged GameObject. You can manually add particles to the system using GetParticles and write them back to the particle system with SetParticles.

The way particles are colored seems to depend on the distance to the camera. This should be implemented with a custom shader which uses the z-depth to pick the particle color. When you already have to write a custom shader anyway, you could also use it for a LOD (level of detail) optimization. After a certain z-depth, skip most of the particles, but render the remaining particles larger. That means far away areas are rendered with a lower level of detail. The player won't notice the difference, but it will be far easier on the GPU.

\$\endgroup\$
2
  • \$\begingroup\$ How many dots could a particle system support? \$\endgroup\$
    – Spooky
    Dec 30, 2022 at 23:13
  • \$\begingroup\$ @Spooky That depends on the target platform. Why don't you try it out yourself? \$\endgroup\$
    – Philipp
    Dec 31, 2022 at 0:33
1
\$\begingroup\$

It's probably not billions of dots but "just" 10s of millions. Also if the points are the only thing being rendered then you will have plenty of memory to spare for all those points.

gpus are pretty good at rendering pointclouds. So the points will just get pushed into a vbo and that gets rendered with a standard vertex shader using GL_POINTS.

The points themselves are acquired using a standard raycast based on the scanner location and direction it's pointing.

Doing a partition of the world to avoid having to render points behind the player and maybe a way to reduce the number of points rendered when far away (LOD), but that's about it for possible optimizations.

\$\endgroup\$
1
\$\begingroup\$

Idea Compute Shader:

  1. take z-buffer and take random screen points(with blue noise sampling).

  2. convert screen point to ray and multiply by distance from z-buffer.

  3. fill RWStructuredBuffer

Visualisation Shader:

  1. use RWStructuredBuffer

  2. convert point to bilboard in geometry shader

  3. add Color

But, i dont have simple and fast idea for blue noise sampaling

This is my scanner shader. For visulizing use any point cloud shader with geometry shader

#pragma kernel Scan
#include "UnityCG.cginc"
#include "./Camera.cginc"

struct Point
{
    float3 position;
    float3 color;
};

struct ScreenPoint
{
    float2 pos;
};

Texture2D<float> depth;
Texture2D<float3> gradient;
Texture2D<float3> frame;
float3 cameraPosition;
float2 nearFarClip;
RWStructuredBuffer<Point> outBuffer;
float time;
matrix InvVP;
int stride;

SamplerState my_point_clamp_sampler;

float LinearEyeDepth2( float rawdepth )
{
    float _NearClip = nearFarClip.x;
    float _FarClip = nearFarClip.y;
    float x, y, z, w;
/*
    x = -1.0 + _NearClip/ _FarClip;
    y = 1;
    z = x / _NearClip;
    w = 1 / _NearClip;
*/

    x = 1.0 - _NearClip/ _FarClip;
    y = _NearClip / _FarClip;
    z = x / _NearClip;
    w = y / _NearClip;


  return 1.0 / (z * rawdepth + w);
}


[numthreads(256,1,1)]
void Scan(uint3 id : SV_DispatchThreadID)
{
    half threads = 256;
    half even = 2.0*(id.x&1)-1;
    half x = even*(sin(time + 0.005*id.x));
    half y = 1-sin(((2*id.x)/threads)-2)*1.5-1.8;
    float2 pPos = float2(x, y);

    float2 uv = float2(0.5f*(pPos.xy+1));
    float z = asfloat(depth.SampleLevel(my_point_clamp_sampler, uv, 0));
    float dist = LinearEyeDepth2(z);

    float4 clip = float4(pPos.x, pPos.y, 0.5f, 1);
    float3 dir = mul(InvVP, clip).xyz;
    float3 pos = dist * dir + cameraPosition;

    float3 col = frame.SampleLevel(my_point_clamp_sampler, uv, 0).xyz;

    half t = step(dist, nearFarClip.y- 0.01);
    pos = lerp(float3(0,0,0), pos, t);


    int index = stride * threads + id.x;
    outBuffer[index].color = col;
    outBuffer[index].position = pos;
}

Inverse VP matrix

private Matrix4x4 GetInvVPMatrix()
{
   var p = GL.GetGPUProjectionMatrix(_camera.projectionMatrix, false);
   var v = GetVMatrix();
   var clipToWorld = Matrix4x4.Inverse(p * v) * Matrix4x4.TRS(new Vector3(0, 0, -p[2,2]), Quaternion.identity, Vector3.one); 
   return clipToWorld;
}

private Matrix4x4 GetVMatrix()
{
    var m = Matrix4x4.TRS(Vector3.zero, _camera.transform.rotation, Vector3.one);
    m = Matrix4x4.Inverse(m);
    m.m20 *= -1f;
    m.m21 *= -1f;
    m.m22 *= -1f;
    m.m23 *= -1f;
    return m;
 }
\$\endgroup\$
0
\$\begingroup\$

Well, i can't be sure, but a custom shader that would only render the vertices of a mesh should do the trick, and i think its the cheapest sollution. Something similar to a wireframe shader.

\$\endgroup\$
3
  • \$\begingroup\$ Sounds like a nice solution, however I believe in this case it's wrong. In this game you can just totally cover every surface with dots, not only put them on vertices. And generating additional milions of vertices would probably cause huge fps drops. \$\endgroup\$
    – dargemir
    Oct 9, 2017 at 8:15
  • \$\begingroup\$ combination of decal techniques then/ \$\endgroup\$
    – Sidar
    Oct 9, 2017 at 10:33
  • \$\begingroup\$ @dargemir if the points are consistently in the same places, then that position information needs to be stored and transformed somewhere! A vertex buffer of positions is about as lightweight as we can hope for. It's even lighter than a Particle System that tries to render full quads for every point, with animation parameters. And it's the same as ratchet freak's suggestion to render the mesh as a point cloud - that also draws only vertices. In Unity you'd do this not with a custom shader, but by setting the Mesh Topology to Points \$\endgroup\$
    – DMGregory
    Oct 9, 2017 at 14:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .