Unity - эффективные опции для загрузки большого количества вершин в ComputeShader для трассировки лучей? - PullRequest
0 голосов
/ 18 октября 2018

Я проводил самостоятельные тренировки в ComputeShaders с использованием великолепных обучающих программ по отслеживанию лучей Unity от Daerst (из http://blog.three -eyed-games.com / 2018/05/03 / gpu-ray-tracing-in-unity-part-1 / ).

В настоящее время я занимаюсь расширением raytracer для приема произвольных объектов сетки, чтобы я мог визуализировать другие объекты, а не только сферы и плоскости.Я написал свою собственную реализацию Moeller-Trombore, используя реализацию руководства из Википедии, и она работала, как и ожидалось, для небольшого числа треугольников (порядка 700).

Однако я обнаружил, что написание меша с 80000вершины в буфер занимают непристойное количество времени порядка минут.Я знаю, что это число вершин довольно низкое в соответствии со стандартами рендеринга, поэтому я полагаю, что в способе его обработки должно быть что-то, что вызывает проблемы с производительностью.

Для пояснения, моя проблема с производительностью не в терминахFPS после загрузки меша - для моих целей все, что больше, чем .1FPS, отлично!Он загружает меш, который кажется слишком медленным.

Вот мой код ComputeShader:

#pragma kernel CSMain

RWTexture2D<float4> Result;

float4x4 _CameraToWorld;
float4x4 _CameraInverseProjection;

float4 _DirectionalLight;

float2 _PixelOffset;

Texture2D<float4> _SkyboxTexture;
SamplerState sampler_SkyboxTexture;

static const float PI = 3.14159265f;

//-------------------------------------
//- UTILITY

float sdot(float3 x, float3 y, float f = 1.0f)
{
    return saturate(dot(x, y) * f);
}

float energy(float3 color)
{
    return dot(color, 1.0f / 3.0f); 
}

//-------------------------------------
//- RANDOMNESS

float2 _Pixel;
float _Seed;

float rand()
{
    float result = frac(sin(_Seed / 100.0f * dot(_Pixel, float2(12.9898f, 78.233f))) * 43758.5453f);
    _Seed += 1.0f;
    return result;
}


struct Geometry
{
    uint type; //4;
    float smoothness; //8;
    float3 albedo; //20;
    float3 specular; //32;
    float3 emission; //44;
    int3 verts; //56
};
StructuredBuffer<Geometry> _Geometries;
StructuredBuffer<float3> _Vertices;


//-------------------------------------
//- RAY

struct Ray
{
    float3 origin;
    float3 direction;
    float3 energy;
};

Ray CreateRay(float3 origin, float3 direction)
{
    Ray ray;
    ray.origin = origin;
    ray.direction = direction;
    ray.energy = float3(1.0f, 1.0f, 1.0f);
    return ray;
}

Ray CreateCameraRay(float2 uv)
{
    // Transform the camera origin to world space
    float3 origin = mul(_CameraToWorld, float4(0.0f, 0.0f, 0.0f, 1.0f)).xyz;

    // Invert the perspective projection of the view-space position
    float3 direction = mul(_CameraInverseProjection, float4(uv, 0.0f, 1.0f)).xyz;
    // Transform the direction from camera to world space and normalize
    direction = mul(_CameraToWorld, float4(direction, 0.0f)).xyz;
    direction = normalize(direction);

    return CreateRay(origin, direction);
}


//-------------------------------------
//- RAYHIT

struct RayHit
{
    float3 position;
    float distance;
    float3 normal;
    float3 albedo;
    float3 specular;
    float smoothness;
    float3 emission;
};

RayHit CreateRayHit()
{
    RayHit hit;
    hit.position = float3(0.0f, 0.0f, 0.0f);
    hit.distance = 1.#INF;
    hit.normal = float3(0.0f, 0.0f, 0.0f);
    hit.albedo = float3(0.0f, 0.0f, 0.0f);
    hit.specular = float3(0.0f, 0.0f, 0.0f);
    hit.smoothness = 0.0f;
    hit.emission = float3(0.0f, 0.0f, 0.0f);
    return hit;
}



//-------------------------------------
//- POLYGONS

struct Triangle
{
    float3 vertexA; //12
    float3 vertexB; //24
    float3 vertexC; //36
    float3 albedo; //48
    float3 specular; //60
    float smoothness; //64
    float3 emission; //76
};

float3 GetTriangleNormal(float3 vA, float3 vB, float3 vC)
{
    return cross(vB-vA, vC-vA);
}


Triangle TriangleFromGeometry(Geometry geometry)
{
    Triangle tri;
    tri.albedo = geometry.albedo;
    tri.specular = geometry.specular;
    tri.smoothness = geometry.smoothness;
    tri.emission = geometry.emission;

    tri.vertexA = _Vertices[geometry.verts[0]];
    tri.vertexB = _Vertices[geometry.verts[1]];
    tri.vertexC = _Vertices[geometry.verts[2]];
    return tri;
}

void IntersectTriangle(Ray ray, inout RayHit bestHit, Triangle tri) 
{
    float epsilon = 0.0000001;
    float3 pA = tri.vertexA;
    float3 pB = tri.vertexB;
    float3 pC = tri.vertexC;
    float3 edge1 = pB - pA;
    float3 edge2 = pC - pA;
    float3 rayVector = ray.direction;// - ray.origin;
    float3 h = cross(rayVector, edge2);
    float a = dot(edge1, h);
    if (a > -epsilon && a < epsilon)
    {
        return;
    }
    float f = 1/a;
    float3 s = ray.origin - pA;
    float u = f * dot(s, h);
    if (u < 0.0f || u> 1.0f)
    {
        return;
    }

    float3 q = cross(s, edge1);
    float v = f * dot(rayVector, q);
    if (v < 0.0 || u + v > 1.0)
    {
        return;
    }
    float t = f * dot(edge2, q);
    if (t > epsilon && t < bestHit.distance)
    {
        bestHit.distance = t;
        bestHit.position = ray.origin + rayVector * t;
        bestHit.normal = GetTriangleNormal(pA, pB, pC);
        bestHit.albedo = tri.albedo;
        bestHit.specular = tri.specular;
        bestHit.smoothness = tri.smoothness;
        bestHit.emission = tri.emission;
    }
}

void IntersectGeometry(Ray ray, inout RayHit bestHit, Geometry geometry) {
    if (geometry.type == 1) {
        Triangle tri = TriangleFromGeometry(geometry);
        IntersectTriangle(ray, bestHit,tri);
    }
}

//-------------------------------------
//- TRACE

RayHit Trace(Ray ray)
{
    RayHit bestHit = CreateRayHit();

    uint numGeometries, geometryStride;
    _Geometries.GetDimensions(numGeometries, geometryStride);
    for (uint i = 0; i < numGeometries; i++) 
    {
        IntersectGeometry(ray, bestHit, _Geometries[i]);
    }

    return bestHit;
}


//-------------------------------------
//- SAMPLING

float3x3 GetTangentSpace(float3 normal)
{
    // Choose a helper vector for the cross product
    float3 helper = float3(1, 0, 0);
    if (abs(normal.x) > 0.99f)
        helper = float3(0, 0, 1);

    // Generate vectors
    float3 tangent = normalize(cross(normal, helper));
    float3 binormal = normalize(cross(normal, tangent));
    return float3x3(tangent, binormal, normal);
}

float3 SampleHemisphere(float3 normal, float alpha)
{
    // Sample the hemisphere, where alpha determines the kind of the sampling
    float cosTheta = pow(rand(), 1.0f / (alpha + 1.0f));
    float sinTheta = sqrt(1.0f - cosTheta * cosTheta);
    float phi = 2 * PI * rand();
    float3 tangentSpaceDir = float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);

    // Transform direction to world space
    return mul(tangentSpaceDir, GetTangentSpace(normal));
}

//-------------------------------------
//- SHADE

float SmoothnessToPhongAlpha(float s)
{
    return pow(1000.0f, s * s);
}

float3 Shade(inout Ray ray, RayHit hit)
{
    if (hit.distance < 1.#INF)
    {
        // Calculate chances of diffuse and specular reflection
        hit.albedo = min(1.0f - hit.specular, hit.albedo);
        float specChance = energy(hit.specular);
        float diffChance = energy(hit.albedo);

        // Roulette-select the ray's path
        float roulette = rand();
        if (roulette < specChance)
        {
            // Specular reflection
            ray.origin = hit.position + hit.normal * 0.001f;
            float alpha = SmoothnessToPhongAlpha(hit.smoothness);
            ray.direction = SampleHemisphere(reflect(ray.direction, hit.normal), alpha);
            float f = (alpha + 2) / (alpha + 1);
            ray.energy *= (1.0f / specChance) * hit.specular * sdot(hit.normal, ray.direction, f);
        }
        else if (diffChance > 0 && roulette < specChance + diffChance)
        {
            // Diffuse reflection
            ray.origin = hit.position + hit.normal * 0.001f;
            ray.direction = SampleHemisphere(hit.normal, 1.0f);
            ray.energy *= (1.0f / diffChance) * hit.albedo;
        }
        else
        {
            // Terminate ray
            ray.energy = 0.0f;
        }

        return hit.emission;
    }
    else
    {
        // Erase the ray's energy - the sky doesn't reflect anything
        ray.energy = 0.0f;

        // Sample the skybox and write it
        float theta = acos(ray.direction.y) / -PI;
        float phi = atan2(ray.direction.x, -ray.direction.z) / -PI * 0.5f;
        return _SkyboxTexture.SampleLevel(sampler_SkyboxTexture, float2(phi, theta), 0).xyz;
    }
}






//-------------------------------------
//- KERNEL

[numthreads(16,16,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    _Pixel = id.xy;

    // Get the dimensions of the RenderTexture
    uint width, height;
    Result.GetDimensions(width, height);

    // Transform pixel to [-1,1] range
    float2 uv = float2((id.xy + _PixelOffset) / float2(width, height) * 2.0f - 1.0f);

    // Get a ray for the UVs
    Ray ray = CreateCameraRay(uv);

    // Trace and shade the ray
    float3 result = float3(0, 0, 0);
    for (int i = 0; i < 4; i++)
    {
        RayHit hit = Trace(ray);
        result += ray.energy * Shade(ray, hit);

        if (!any(ray.energy))
            break;
    }

    Result[id.xy] = float4(result, 1);
}

и вот класс C #, который записывает информацию в шейдер:

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Profiling;

public class RayTracingMaster : MonoBehaviour
{
    public ComputeShader RayTracingShader;
    private RenderTexture _target;
    public Texture SkyboxTexture;
    private uint _currentSample = 0;
    private Material _addMaterial;

    private ComputeBuffer _geometryBuffer;
    private ComputeBuffer _vertexBuffer;
    private Camera _camera;
    public Light DirectionalLight;
    private RenderTexture _converged;
    public int SphereSeed = 2018;
    public List<Geometry> geometries = new List<Geometry>();
    private List<Vector3> _vertices = new List<Vector3>();


    private void Awake()
    {
        _camera = GetComponent<Camera>();
    }


    private void Update()
    {
        if (transform.hasChanged)
        {
            _currentSample = 0;
            transform.hasChanged = false;
        }

        if (DirectionalLight.transform.hasChanged)
        {
            _currentSample = 0;
            DirectionalLight.transform.hasChanged = false;
        }
    }
    private void OnEnable()
    {
        _currentSample = 0;
        SetUpScene();
    }
    private void OnDisable()
    {
        if (_geometryBuffer != null)
            _geometryBuffer.Release();
    }
    private void SetUpScene()
    {
        Random.InitState(SphereSeed);
        Profiler.BeginSample("Geometry Buffer Creation");
        _geometryBuffer = new ComputeBuffer(geometries.Count, 56);
        _geometryBuffer.SetData(geometries);
        _vertexBuffer = new ComputeBuffer(_vertices.Count, 12);
        _vertexBuffer.SetData(_vertices);
        Profiler.EndSample();

    }

    private void SetShaderParameters()
    {
        RayTracingShader.SetMatrix("_CameraToWorld", _camera.cameraToWorldMatrix);
        RayTracingShader.SetMatrix("_CameraInverseProjection", _camera.projectionMatrix.inverse);

        RayTracingShader.SetVector("_PixelOffset", new Vector2(Random.value, Random.value));
        Vector3 l = DirectionalLight.transform.forward;
        RayTracingShader.SetVector("_DirectionalLight", new Vector4(l.x, l.y, l.z, DirectionalLight.intensity));
        RayTracingShader.SetInt("numGeometry",_geometryBuffer.count);
        Debug.Log("Geometries: " + _geometryBuffer.count);
        RayTracingShader.SetBuffer(0, "_Geometries", _geometryBuffer);
        RayTracingShader.SetBuffer(0, "_Vertices", _vertexBuffer);
        RayTracingShader.SetFloat("_Seed", Random.value);
        RayTracingShader.SetTexture(0, "_SkyboxTexture", SkyboxTexture);
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        SetShaderParameters();
        Render(destination);
    }
    private void Render(RenderTexture destination)
    {
        // Make sure we have a current render target
        InitRenderTexture();
        // Set the target and dispatch the compute shader
        RayTracingShader.SetTexture(0, "Result", _target);
        int threadGroupsX = Mathf.CeilToInt(Screen.width / 8.0f);
        int threadGroupsY = Mathf.CeilToInt(Screen.height / 8.0f);
        RayTracingShader.Dispatch(0, threadGroupsX, threadGroupsY, 1);
        // Blit the result texture to the screen
        if (_addMaterial == null)
            _addMaterial = new Material(Shader.Find("Hidden/AddShader"));
        _addMaterial.SetFloat("_Sample", _currentSample);
        Graphics.Blit(_target, destination, _addMaterial);
        _currentSample++;
    }
    private void InitRenderTexture()
    {
        if ((_target == null || _target.width != Screen.width || _target.height != Screen.height) || (_converged == null || _converged.width != Screen.width || _converged.height != Screen.height))
        {
            // Release render texture if we already have one
            if (_target != null)
                _target.Release();
            // Get a render target for Ray Tracing
            _target = new RenderTexture(Screen.width, Screen.height, 0,
                RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
            _target.enableRandomWrite = true;
            _target.Create();

            if (_converged != null)
            {
                _converged.Release();
            }
            _converged = new RenderTexture(Screen.width, Screen.height, 0, RenderTextureFormat.ARGBFloat,RenderTextureReadWrite.Linear);
            _converged.enableRandomWrite = true;
            _converged.Create();
            _currentSample = 0;
        }
    }

    public Geometry[] ShaderGeometryFromMesh(Mesh mesh, Vector3 albedo, Vector3 specular, float smoothness, Vector3 emission, Vector3 worldScale, Quaternion worldRotation) {
       Profiler.BeginSample("Shader Geometry Creation");
        Geometry[] geometry = new Geometry[mesh.triangles.Length/3];
        for (int i = 0; i < geometry.Length; i++)
        {
            int[] vertIndices = new int[3];
            for (int j = 0; j < 3; j++)
            {
                Vector3 vert = mesh.vertices[mesh.triangles[i * 3 + j]];
                if (!_vertices.Contains(vert))
                {
                    _vertices.Add(vert);
                }
                vertIndices[j] = _vertices.IndexOf(vert);
            }
            geometry[i] = new Geometry(albedo, specular, smoothness, emission,vertIndices);
        }
        Profiler.EndSample();
        return geometry;

    }





    public struct Geometry
    {
        public uint type;
        public Vector3 albedo;
        public Vector3 specular;
        public float smoothness;
        public Vector3 emission;

        public Vector3Int verts;

        public Geometry( Vector3 albedo, Vector3 specular, float smoothness, Vector3 emission, int[] verts, uint type = 1) {
            this.albedo = albedo;
            this.specular = specular;
            this.smoothness = smoothness;
            this.emission = emission;
            this.verts = new Vector3Int(verts[0],verts[1],verts[2]);
            this.type = type;
        }
    }
}

1 Ответ

0 голосов
/ 20 октября 2018

Оказывается, у меня не было проблемы, я думал, что у меня вообще!моим узким местом было то, что метод ShaderGeometryFromMesh () пытался избежать дублирования вершин, проверяя, является ли каждая добавляемая вершина уникальной.Очевидно, что это заняло уйму времени, так как список расширился до тысяч или десятков тысяч наименований.Загрузка в буфер относительно тривиальна.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...