Ejemplo n.º 1
0
        public virtual void Render(SSRenderConfig renderConfig)
        {
            // compute and set the modelView matrix, by combining the cameraViewMat
            // with the object's world matrix
            //    ... http://www.songho.ca/opengl/gl_transform.html
            //    ... http://stackoverflow.com/questions/5798226/3d-graphics-processing-how-to-calculate-modelview-matrix
            renderBoundingSphereMesh(renderConfig);

            Matrix4 modelViewMat = this.worldMat * renderConfig.invCameraViewMatrix;

            if (this.renderState.matchScaleToScreenPixels)
            {
                Matrix4 scaleCompenstaion = OpenTKHelper.ScaleToScreenPxViewMat(
                    this.Pos, this.Scale.X, ref renderConfig.invCameraViewMatrix, ref renderConfig.projectionMatrix);
                modelViewMat = scaleCompenstaion * modelViewMat;
            }
            if (this.renderState.doBillboarding)
            {
                modelViewMat = OpenTKHelper.BillboardMatrix(ref modelViewMat);
            }
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref modelViewMat);

            resetTexturingState();

            if (renderConfig.drawingShadowMap)
            {
                if (renderConfig.drawingPssm && renderConfig.pssmShader != null)
                {
                    renderConfig.pssmShader.Activate();
                    renderConfig.pssmShader.UniObjectWorldTransform = this.worldMat;
                }
                else
                {
                    SSShaderProgram.DeactivateAll();
                }
            }
            else
            {
                if (renderState.noShader)
                {
                    SSShaderProgram.DeactivateAll();
                }
                else
                {
                    setDefaultShaderState(renderConfig.mainShader, renderConfig);
                }
                setMaterialState();

                if (this.alphaBlendingEnabled)
                {
                    GL.Enable(EnableCap.Blend);
                    GL.BlendEquationSeparate(renderState.blendEquationModeRGB, renderState.blendEquationModeAlpha);
                    GL.BlendFuncSeparate(renderState.blendFactorSrcRGB, renderState.blendFactorDestRGB,
                                         renderState.blendFactorSrcAlpha, renderState.blendFactorDestAlpha);
                    //GL.B
                }
                else
                {
                    GL.Disable(EnableCap.Blend);
                }

                if (this.renderState.lighted)
                {
                    GL.Enable(EnableCap.Lighting);
                    GL.ShadeModel(ShadingModel.Flat);
                }
                else
                {
                    GL.Disable(EnableCap.Lighting);
                }
            }

            if (this.renderState.alphaTest)
            {
                GL.Enable(EnableCap.AlphaTest);
            }
            else
            {
                GL.Disable(EnableCap.AlphaTest);
            }

            if (this.renderState.depthTest)
            {
                GL.Enable(EnableCap.DepthTest);
                GL.DepthFunc(renderState.depthFunc);
            }
            else
            {
                GL.Disable(EnableCap.DepthTest);
            }
            GL.DepthMask(this.renderState.depthWrite);

            GL.Disable(EnableCap.LineStipple);

            if (preRenderHook != null)
            {
                preRenderHook(this, renderConfig);
            }
        }
        protected void ComputeProjections(
            List <SSObject> objects,
            Matrix4 cameraView,
            Matrix4 cameraProj,
            float fov, float aspect, float cameraNearZ, float cameraFarZ)
        {
            if (m_light.GetType() != typeof(SSDirectionalLight))
            {
                throw new NotSupportedException();
            }
            SSDirectionalLight dirLight = (SSDirectionalLight)m_light;

            // light-aligned unit vectors
            Vector3 lightZ = dirLight.Direction.Normalized();
            Vector3 lightX, lightY;

            OpenTKHelper.TwoPerpAxes(lightZ, out lightX, out lightY);
            // transform matrix from regular space into light aligned space
            Matrix4 lightTransform = new Matrix4(
                lightX.X, lightX.Y, lightX.Z, 0f,
                lightY.X, lightY.Y, lightY.Z, 0f,
                lightZ.X, lightZ.Y, lightZ.Z, 0f,
                0f, 0f, 0f, 0f
                );

            // Step 0: camera projection matrix (nearZ and farZ modified) for each frustum split
            float prevFarZ = cameraNearZ;

            for (int i = 0; i < c_numberOfSplits; ++i)
            {
                // generate frustum splits using Practical Split Scheme (GPU Gems 3, 10.2.1)
                float iRatio    = (float)(i + 1) / (float)c_numberOfSplits;
                float cLog      = cameraNearZ * (float)Math.Pow(cameraFarZ / cameraNearZ, iRatio);
                float cUni      = cameraNearZ + (cameraFarZ - cameraNearZ) * iRatio;
                float nextFarZ  = LogVsLinearSplitFactor * cLog + (1f - LogVsLinearSplitFactor) * cUni;
                float nextNearZ = prevFarZ;

                // exported to the shader
                m_viewSplits [i] = nextFarZ;

                // create a view proj matrix with the nearZ, farZ values for the current split
                m_frustumViewProjMatrices[i] = cameraView
                                               * Matrix4.CreatePerspectiveFieldOfView(fov, aspect, nextNearZ, nextFarZ);

                // create light-aligned AABBs of frustums
                m_frustumLightBB [i] = SSAABB.FromFrustum(ref lightTransform, ref m_frustumViewProjMatrices [i]);

                prevFarZ = nextFarZ;
            }

            #if true
            // Optional scene-dependent optimization
            for (int i = 0; i < c_numberOfSplits; ++i)
            {
                m_objsLightBB[i]   = new SSAABB(float.PositiveInfinity, float.NegativeInfinity);
                m_splitFrustums[i] = new SSFrustumCuller(ref m_frustumViewProjMatrices[i]);
                m_shrink[i]        = false;
            }
            foreach (var obj in objects)
            {
                // pass through all shadow casters and receivers
                if (obj.renderState.toBeDeleted || obj.localBoundingSphereRadius <= 0f ||
                    !obj.renderState.visible || !obj.renderState.receivesShadows)
                {
                    continue;
                }
                else
                {
                    for (int i = 0; i < c_numberOfSplits; ++i)
                    {
                        if (m_splitFrustums[i].isSphereInsideFrustum(obj.worldBoundingSphere))
                        {
                            // determine AABB in light coordinates of the objects so far
                            m_shrink[i] = true;
                            Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform);
                            Vector3 rad             = new Vector3(obj.worldBoundingSphereRadius);
                            Vector3 localMin        = lightAlignedPos - rad;
                            Vector3 localMax        = lightAlignedPos + rad;

                            m_objsLightBB[i].UpdateMin(localMin);
                            m_objsLightBB[i].UpdateMax(localMax);
                        }
                    }
                }
            }
            #endif

            for (int i = 0; i < c_numberOfSplits; ++i)
            {
                if (m_shrink [i])
                {
                    m_resultLightBB[i].Min = Vector3.ComponentMax(m_frustumLightBB [i].Min,
                                                                  m_objsLightBB [i].Min);
                    m_resultLightBB [i].Max = Vector3.ComponentMin(m_frustumLightBB [i].Max,
                                                                   m_objsLightBB [i].Max);
                }
                else
                {
                    m_resultLightBB [i] = m_frustumLightBB [i];
                }
            }

            for (int i = 0; i < c_numberOfSplits; ++i)
            {
                // Obtain view + projection + crop matrix, need it later
                Matrix4 shadowView, shadowProj;
                viewProjFromLightAlignedBB(ref m_resultLightBB [i], ref lightTransform, ref lightY,
                                           out shadowView, out shadowProj);
                m_shadowViewProjMatrices[i] = shadowView * shadowProj * c_cropMatrices[i];
                // obtain view + projection + clio + bias
                m_shadowViewProjBiasMatrices[i] = m_shadowViewProjMatrices[i] * c_biasMatrix;

                // There is, currently, no mathematically derived solution to how much Poisson spread scaling
                // you need for each split. Current improvisation combines 1) increasing spread for the near
                // splits; reducing spread for the far splits and 2) reducing spread for splits with larger
                // light-aligned areas; increasing spread for splits with smaller light-aligned areas
                m_poissonScaling [i] = m_resultLightBB [i].Diff().Xy / (100f * (float)Math.Pow(3.0, i - 1));
            }

            // Combine all splits' BB into one and extend it to include shadow casters closer to light
            SSAABB castersLightBB = new SSAABB(float.PositiveInfinity, float.NegativeInfinity);
            for (int i = 0; i < c_numberOfSplits; ++i)
            {
                castersLightBB.Combine(ref m_resultLightBB [i]);
            }

            // extend Z of the AABB to cover shadow-casters closer to the light
            foreach (var obj in objects)
            {
                if (obj.renderState.toBeDeleted || obj.localBoundingSphereRadius <= 0f ||
                    !obj.renderState.visible || !obj.renderState.castsShadow)
                {
                    continue;
                }
                else
                {
                    Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform);
                    Vector3 rad             = new Vector3(obj.worldBoundingSphereRadius);
                    Vector3 localMin        = lightAlignedPos - rad;
                    Vector3 localMax        = lightAlignedPos + rad;

                    if (localMin.Z < castersLightBB.Min.Z)
                    {
                        if (OpenTKHelper.RectsOverlap(castersLightBB.Min.Xy,
                                                      castersLightBB.Max.Xy,
                                                      localMin.Xy,
                                                      localMax.Xy))
                        {
                            castersLightBB.Min.Z = localMin.Z;
                        }
                    }
                }
            }

            // Generate frustum culler from the BB extended towards light to include shadow casters
            Matrix4 frustumView, frustumProj;
            viewProjFromLightAlignedBB(ref castersLightBB, ref lightTransform, ref lightY,
                                       out frustumView, out frustumProj);
            Matrix4 frustumMatrix = frustumView * frustumProj;
            FrustumCuller = new SSFrustumCuller(ref frustumMatrix);
        }
Ejemplo n.º 3
0
        public override void sortByDepth(ref Matrix4 viewMatrix)
        {
            if (_numParticles == 0)
            {
                return;
            }

            int   numAlive      = 0;
            float prevViewDepth = float.NegativeInfinity;
            bool  alreadySorted = true;

            for (int i = 0; i < _activeBlockLength; ++i)
            {
                if (isAlive(i))
                {
                    // Do the transform and store z of the result
                    Vector3 pos = _readElement(_positions, i).Value;
                    pos = Vector3.Transform(pos, viewMatrix);
                    float viewDepth = pos.Z;
                    writeDataIfNeeded(ref _viewDepths, i, viewDepth);
                    ++numAlive;
                    if (alreadySorted && pos.Z < prevViewDepth)
                    {
                        alreadySorted = false;
                    }
                    prevViewDepth = viewDepth;
                }
                else
                {
                    // since we are doing a sort pass later, might as well make it so
                    // so the dead particles get pushed the back of the arrays
                    writeDataIfNeeded(ref _viewDepths, i, float.PositiveInfinity);
                    alreadySorted = false; //force a "sort" to puch dead particles towards the end
                }
            }

            if (!alreadySorted)
            {
                quickSort(0, _activeBlockLength - 1);

                // somewhat hacky workaround for zombie particles that seem to be related to sortByDepth()
                _numParticles      = numAlive;
                _activeBlockLength = _numParticles;

                if (_numParticles == 0)
                {
                    _nextIdxToWrite = _nextIdxToOverwrite = 0;
                }
                else if (_numParticles < _capacity)
                {
                    // update pointers to reflect dead particles that just got sorted to the back
                    _nextIdxToWrite = _numParticles - 1;
                }
                #if DEBUG_PARTICLE_SORTING
                ++debugNumSorted;
                #endif
            }
            else
            {
                #if DEBUG_PARTICLE_SORTING
                ++debugNumSortSkipped;
                #endif
            }

            #if DEBUG_PARTICLE_SORTING
            System.Console.WriteLine(
                "particle data actually sorted "
                + ((float)debugNumSorted / (float)(debugNumSorted + debugNumSortSkipped) * 100f)
                + "% of the time");
            #endif

            #if false
            // set color based on the index of the particle in the arrays
            SSAttributeColor[] debugColors =
            {
                new SSAttributeColor(OpenTKHelper.Color4toRgba(Color4.Red)),
                new SSAttributeColor(OpenTKHelper.Color4toRgba(Color4.Green)),
                new SSAttributeColor(OpenTKHelper.Color4toRgba(Color4.Blue)),
                new SSAttributeColor(OpenTKHelper.Color4toRgba(Color4.Yellow))
            };

            for (int i = 0; i < m_activeBlockLength; ++i)
            {
                writeDataIfNeeded(ref m_colors, i, debugColors[i]);
            }
            #endif
        }
Ejemplo n.º 4
0
        public SSShadowMapBase(TextureUnit texUnit, int textureWidth, int textureHeight)
        {
            if (!OpenTKHelper.areFramebuffersSupported())
            {
                m_isValid = false;
                return;
            }
            #if false
            if (s_numberOfShadowMaps >= c_maxNumberOfShadowMaps)
            {
                throw new Exception("Unsupported number of shadow maps: "
                                    + (c_maxNumberOfShadowMaps + 1));
            }
            #endif
            ++s_numberOfShadowMaps;

            m_frameBufferID = GL.Ext.GenFramebuffer();
            if (m_frameBufferID < 0)
            {
                throw new Exception("gen fb failed");
            }
            m_textureID     = GL.GenTexture();
            m_textureWidth  = textureWidth;
            m_textureHeight = textureHeight;

            // bind the texture and set it up...
            m_textureUnit = texUnit;
            BindShadowMapToTexture();
            GL.TexParameter(TextureTarget.Texture2D,
                            TextureParameterName.TextureMagFilter,
                            (int)TextureMagFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D,
                            TextureParameterName.TextureMinFilter,
                            (int)TextureMinFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D,
                            TextureParameterName.TextureWrapS,
                            (int)TextureWrapMode.ClampToEdge);
            GL.TexParameter(TextureTarget.Texture2D,
                            TextureParameterName.TextureWrapT,
                            (int)TextureWrapMode.ClampToEdge);

            GL.TexImage2D(TextureTarget.Texture2D, 0,
                          PixelInternalFormat.DepthComponent32f,
                          m_textureWidth, m_textureHeight, 0,
                          PixelFormat.DepthComponent, PixelType.Float, IntPtr.Zero);
            // done creating texture, unbind
            GL.BindTexture(TextureTarget.Texture2D, 0);


            // ----------------------------
            // now bind the texture to framebuffer..
            GL.Ext.BindFramebuffer(FramebufferTarget.DrawFramebuffer, m_frameBufferID);
            GL.Ext.BindFramebuffer(FramebufferTarget.ReadFramebuffer, m_frameBufferID);

            GL.Ext.FramebufferTexture2D(FramebufferTarget.DrawFramebuffer, FramebufferAttachment.DepthAttachment,
                                        TextureTarget.Texture2D, m_textureID, 0);
            //GL.Ext.FramebufferTexture (FramebufferTarget.FramebufferExt, FramebufferAttachment.Color,
            //(int)All.None, 0);

            GL.Viewport(0, 0, m_textureWidth, m_textureHeight);
            GL.DrawBuffer(DrawBufferMode.None);
            GL.ReadBuffer(ReadBufferMode.None);

            if (!assertFramebufferOK(FramebufferTarget.DrawFramebuffer))
            {
                throw new Exception("failed to create-and-bind shadowmap FBO");
            }

            // leave in a sane state...
            unbindFramebuffer();
            GL.ActiveTexture(TextureUnit.Texture0);
            if (!assertFramebufferOK(FramebufferTarget.DrawFramebuffer))
            {
                throw new Exception("failed to ubind shadowmap FBO");
            }
        }
Ejemplo n.º 5
0
        private void ComputeProjections(List <SSObject> objects,
                                        SSLightBase light,
                                        Matrix4 cameraView, Matrix4 cameraProj)
        {
            if (light.GetType() != typeof(SSDirectionalLight))
            {
                throw new NotSupportedException();
            }
            SSDirectionalLight dirLight = (SSDirectionalLight)light;

            // light-aligned unit vectors
            Vector3 lightZ = dirLight.Direction.Normalized();
            Vector3 lightX, lightY;

            OpenTKHelper.TwoPerpAxes(lightZ, out lightX, out lightY);
            // transform matrix from regular space into light aligned space
            Matrix4 lightTransform = new Matrix4(
                lightX.X, lightX.Y, lightX.Z, 0f,
                lightY.X, lightY.Y, lightY.Z, 0f,
                lightZ.X, lightZ.Y, lightZ.Z, 0f,
                0f, 0f, 0f, 0f
                );

            // Find AABB of frustum corners in light coordinates
            Matrix4 cameraViewProj = cameraView * cameraProj;
            SSAABB  frustumLightBB = SSAABB.FromFrustum(ref lightTransform, ref cameraViewProj);

            bool   shrink      = false;
            SSAABB objsLightBB = new SSAABB(float.PositiveInfinity, float.NegativeInfinity);

            #if true
            // (optional) scene dependent optimization
            // Trim the light-bounding box by the shadow receivers (only in light-space x,y,maxz)
            SSFrustumCuller cameraFrustum = new SSFrustumCuller(ref cameraViewProj);

            foreach (var obj in objects)
            {
                // pass through all shadow casters and receivers
                if (obj.renderState.toBeDeleted || !obj.renderState.visible ||
                    !obj.renderState.receivesShadows || obj.localBoundingSphereRadius <= 0f)
                {
                    continue;
                }
                else if (cameraFrustum.isSphereInsideFrustum(obj.worldBoundingSphere))
                {
                    // determine AABB in light coordinates of the objects so far
                    shrink = true;
                    Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform);
                    Vector3 rad             = new Vector3(obj.worldBoundingSphereRadius);
                    Vector3 localMin        = lightAlignedPos - rad;
                    Vector3 localMax        = lightAlignedPos + rad;
                    objsLightBB.UpdateMin(localMin);
                    objsLightBB.UpdateMax(localMax);
                }
            }
            #endif

            // Optimize the light-frustum-projection bounding box by the object-bounding-box
            SSAABB resultLightBB = new SSAABB(float.PositiveInfinity, float.NegativeInfinity);
            if (shrink)
            {
                // shrink the XY & far-Z coordinates..
                resultLightBB.Min.Xy = Vector2.ComponentMax(frustumLightBB.Min.Xy, objsLightBB.Min.Xy);
                resultLightBB.Min.Z  = objsLightBB.Min.Z;
                resultLightBB.Max    = Vector3.ComponentMin(frustumLightBB.Max, objsLightBB.Max);
            }
            else
            {
                resultLightBB = frustumLightBB;
            }

            // View and projection matrices, used by the scene later
            viewProjFromLightAlignedBB(ref resultLightBB, ref lightTransform, ref lightY,
                                       out m_shadowViewMatrix, out m_shadowProjMatrix);

            // Now extend Z of the result AABB to cover shadow-casters closer to the light inside the
            // original box
            foreach (var obj in objects)
            {
                if (obj.renderState.toBeDeleted || !obj.renderState.visible ||
                    !obj.renderState.castsShadow || obj.localBoundingSphereRadius <= 0f)
                {
                    continue;
                }
                Vector3 lightAlignedPos = Vector3.Transform(obj.worldBoundingSphereCenter, lightTransform);
                Vector3 rad             = new Vector3(obj.worldBoundingSphereRadius);
                Vector3 localMin        = lightAlignedPos - rad;
                if (localMin.Z < resultLightBB.Min.Z)
                {
                    Vector3 localMax = lightAlignedPos + rad;
                    if (OpenTKHelper.RectsOverlap(resultLightBB.Min.Xy,
                                                  resultLightBB.Max.Xy,
                                                  localMin.Xy,
                                                  localMax.Xy))
                    {
                        resultLightBB.Min.Z = localMin.Z;
                    }
                }
            }

            // Generate frustum culler from the BB extended towards light to include shadow casters
            Matrix4 frustumView, frustumProj;
            viewProjFromLightAlignedBB(ref resultLightBB, ref lightTransform, ref lightY,
                                       out frustumView, out frustumProj);
            Matrix4 frustumMatrix = frustumView * frustumProj;
            FrustumCuller = new SSFrustumCuller(ref frustumMatrix);
        }
Ejemplo n.º 6
0
        //---------------------

        public override bool preciseIntersect(ref SSRay localRay, out float nearestLocalRayContact)
        {
            nearestLocalRayContact = float.PositiveInfinity;

            if (useBVHForIntersections)
            {
                if (_bvh == null && _vbo != null && _ibo != null)
                {
                    // rebuilding BVH
                    // TODO try updating instead of rebuilding?
                    _bvh = new SSIndexedMeshTrianglesBVH(_vbo, _ibo);
                    for (UInt16 triIdx = 0; triIdx < _ibo.numIndices / 3; ++triIdx)
                    {
                        _bvh.addObject(triIdx);
                    }
                    Console.WriteLine("New BVH MaxDepth = {0}", _bvh.maxDepth);
                }

                if (_bvh != null)
                {
                    List <ssBVHNode <UInt16> > nodesHit = _bvh.traverseRay(localRay);
                    foreach (var node in nodesHit)
                    {
                        if (!node.IsLeaf)
                        {
                            continue;
                        }

                        foreach (UInt16 triIdx in node.gobjects)
                        {
                            Vector3 v0, v1, v2;
                            _readTriangleVertices(triIdx, out v0, out v1, out v2);

                            float contact;
                            if (OpenTKHelper.TriangleRayIntersectionTest(
                                    ref v0, ref v1, ref v2, ref localRay.pos, ref localRay.dir, out contact))
                            {
                                if (contact < nearestLocalRayContact)
                                {
                                    nearestLocalRayContact = contact;
                                }
                            }
                        }
                    }
                }
            }
            else
            {
                _bvh = null;
                // slow, tedious intersection test
                int numTri = lastAssignedIndices.Length / 3;
                for (UInt16 triIdx = 0; triIdx < numTri; ++triIdx)
                {
                    Vector3 v0, v1, v2;
                    _readTriangleVertices(triIdx, out v0, out v1, out v2);
                    float contact;
                    if (OpenTKHelper.TriangleRayIntersectionTest(
                            ref v0, ref v1, ref v2, ref localRay.pos, ref localRay.dir, out contact))
                    {
                        if (contact < nearestLocalRayContact)
                        {
                            nearestLocalRayContact = contact;
                        }
                    }
                }
            }
            return(nearestLocalRayContact < float.PositiveInfinity);
        }
Ejemplo n.º 7
0
            //protected readonly STrailUpdater _updater;
            #endregion

            public STrailsData(
                PositionFunc positionFunc, FwdFunc fwdDirFunc, UpFunc upFunc,
                STrailsParameters trailsParams = null)
                : base(trailsParams.capacity)
            {
                this.trailsParams  = trailsParams;
                this._positionFunc = positionFunc;
                this._fwdFunc      = fwdDirFunc;
                this._upFunc       = upFunc;

                _headSegmentIdxs            = new ushort[trailsParams.numJets];
                _tailSegmentIdxs            = new ushort[trailsParams.numJets];
                _prevSplineIntervalEndPos   = new Vector3[trailsParams.numJets];
                _prevSplineIntervalEndSlope = new Vector3[trailsParams.numJets];
                _localJetOrients            = new Matrix4[trailsParams.numJets];

                Vector3 pos          = _positionFunc();
                Vector3 fwd          = _fwdFunc();
                Vector3 up           = _upFunc();
                Vector3 right        = Vector3.Cross(fwd, up);
                Matrix4 globalOrient = new Matrix4(
                    new Vector4(right, 0f),
                    new Vector4(up, 0f),
                    new Vector4(fwd, 0f),
                    new Vector4(0f, 0f, 0f, 1f));

                for (int i = 0; i < trailsParams.numJets; ++i)
                {
                    _headSegmentIdxs[i] = STrailsSegment.NotConnected;
                    _tailSegmentIdxs[i] = STrailsSegment.NotConnected;
                    Vector3 localFwd = trailsParams.localJetDir(i);
                    _localJetOrients[i] = OpenTKHelper.neededRotationMat(-Vector3.UnitZ, localFwd);
                    jetTxfm(i, ref pos, ref globalOrient,
                            out _prevSplineIntervalEndPos[i], out _prevSplineIntervalEndSlope[i]);
                }

                _outerColorEffector = new SSColorKeyframesEffector(trailsParams.outerColorKeyframes)
                {
                    particleLifetime = trailsParams.trailLifetime,
                };
                addEffector(_outerColorEffector);

                _innerColorEffector = new STrailsInnerColorEffector(trailsParams.innerColorKeyframes)
                {
                    particleLifetime = trailsParams.trailLifetime,
                };
                addEffector(_innerColorEffector);


                _innerRatioEffector = new STrailsInnerColorRatioEffector(trailsParams.innerColorRatioKeyframes)
                {
                    particleLifetime = trailsParams.trailLifetime,
                };
                addEffector(_innerRatioEffector);

                _outerRatioEffector = new STrailsOuterColorRatioEffector(trailsParams.outerColorRatioKeyframes)
                {
                    particleLifetime = trailsParams.trailLifetime,
                };
                addEffector(_outerRatioEffector);

                _widthEffector = new STrailsWidthEffector(trailsParams.widthKeyFrames)
                {
                    particleLifetime = trailsParams.trailLifetime,
                };
                addEffector(_widthEffector);


                //_updater = new STrailUpdater(trailsParams);
                //addEffector(_updater);
            }
        public override void Render(SSRenderConfig renderConfig)
        {
            Matrix4 modelView = this.worldMat * renderConfig.invCameraViewMatrix;

            // allow particle system to react to camera/worldview
            particleSystem.updateCamera(ref modelView, ref renderConfig.projectionMatrix);

            // do we have anything to draw?
            if (particleSystem.activeBlockLength <= 0)
            {
                return;
            }

            base.Render(renderConfig);

            // select either instance shader or instance pssm shader
            ISSInstancableShaderProgram instanceShader = renderConfig.instanceShader;

            if (renderConfig.drawingShadowMap)
            {
                if (renderConfig.drawingPssm)
                {
                    renderConfig.instancePssmShader.Activate();
                    renderConfig.instancePssmShader.UniObjectWorldTransform = this.worldMat;
                    instanceShader = renderConfig.instancePssmShader;
                }
            }
            else
            {
                if (!globalBillboarding && base.alphaBlendingEnabled)
                {
                    // Must be called before updating buffers
                    particleSystem.sortByDepth(ref modelView);

                    // Fixes flicker issues for particles with "fighting" view depth values
                    // Also assumes the particle system is the last to be drawn in a scene
                    GL.DepthFunc(DepthFunction.Lequal);
                }
                if (depthRead)
                {
                    GL.Enable(EnableCap.DepthTest);
                }
                else
                {
                    GL.Disable(EnableCap.DepthTest);
                }
                GL.DepthMask(depthWrite);

                // texture binding setup
                renderConfig.instanceShader.Activate();
                renderConfig.instanceShader.UniObjectWorldTransform = this.worldMat;
                if (base.textureMaterial != null)
                {
                    renderConfig.instanceShader.SetupTextures(base.textureMaterial);
                }
            }

            if (globalBillboarding)
            {
                // Setup "global" billboarding. (entire particle system is rendered as a camera-facing
                // billboard and will show the same position of particles from all angles)
                modelView = OpenTKHelper.BillboardMatrix(ref modelView);
                GL.MatrixMode(MatrixMode.Modelview);
                GL.LoadMatrix(ref modelView);
            }

            instanceShader.Activate();

            // prepare attribute arrays for draw
            GL.PushClientAttrib(ClientAttribMask.ClientAllAttribBits);
            prepareAttribute(_posBuffer, instanceShader.AttrInstancePos,
                             particleSystem.positions);
            prepareAttribute(_orientationXYBuffer, instanceShader.AttrInstanceOrientationXY,
                             particleSystem.orientationsXY);
            prepareAttribute(_orientationZBuffer, instanceShader.AttrInstanceOrientationZ,
                             particleSystem.orientationsZ);
            prepareAttribute(_masterScaleBuffer, instanceShader.AttrInstanceMasterScale,
                             particleSystem.masterScales);
            prepareAttribute(_componentScaleXYBuffer, instanceShader.AttrInstanceComponentScaleXY,
                             particleSystem.componentScalesXY);
            prepareAttribute(_componentScaleZBuffer, instanceShader.AttrInstanceComponentScaleZ,
                             particleSystem.componentScalesZ);
            prepareAttribute(_colorBuffer, instanceShader.AttrInstanceColor,
                             particleSystem.colors);

            //prepareAttribute(m_spriteIndexBuffer, instanceShader.AttrInstanceSpriteIndex, m_ps.SpriteIndices);
            prepareAttribute(_spriteOffsetUBuffer, instanceShader.AttrInstanceSpriteOffsetU,
                             particleSystem.spriteOffsetsU);
            prepareAttribute(_spriteOffsetVBuffer, instanceShader.AttrInstanceSpriteOffsetV,
                             particleSystem.SpriteOffsetsV);
            prepareAttribute(_spriteSizeUBuffer, instanceShader.AttrInstanceSpriteSizeU,
                             particleSystem.SpriteSizesU);
            prepareAttribute(_spriteSizeVBuffer, instanceShader.AttrInstanceSpriteSizeV,
                             particleSystem.SpriteSizesV);

            // do the draw
            mesh.renderInstanced(renderConfig, particleSystem.activeBlockLength, PrimitiveType.Triangles);

            GL.PopClientAttrib();
            //this.boundingSphere.Render(ref renderConfig);
        }